Last week we went over how to do audits using PowerShell (Link). Today we will use scheduled tasks with PowerShell to have the audit script run hour by hour. We do this because we don’t want to be manually running the PowerShell script every hour. Let the computer handle all of that for us. We will go over how to manually build the Scheduled Task and the PowerShell way.

Manual Process – Scheduled Tasks

Lets take a look at the manual process. We are placing our AuditDisabledAccounts.ps1 script on the computer. I like placing things in the c:\scripts or c:\temp folder. Sometimes this is good, sometimes this is bad. It depends on the world you are working in.

  1. Start Task Scheduler
  2. Click Task Scheduler Library.
  3. Right Click and select basic task
  4. Name it accordingly. I am naming mine “Hourly Disabled AD Audit.”
  5. Under Triggers, I selected When the computer starts.
    • This scheduled task will repeat itself with another setting. It’s best to get it started when the computer starts. This way if the system restarts, it will start again. It can become confusing over time.
  6. The action will be start a program
    • Program: Powershell
    • Arguments: -NoProfile -ExecutionPolicy Bypass -HoursBack 1 -Servers AD1,AD2,AD3 -OutCSVfile “C:\Reports\DisabledAccountsAudit.csv”
    • Start In: c:\temp\AuditDisabledAccounts.ps1
  7. To finish, you want to open the properties dialog

Now we have a basic scheduled task setup. Next we want to have it trigger every hour. Sense we opened the properites you can now do just this.

  1. On the general tab
  2. Radio check: “Run whether the user is logged on or not.”
    • If you need to change the user, this is where you will do that.
  3. Click the Triggers tab.
  4. You will see at startup, click edit
  5. Under advanced Settings
    • Check Repeat task every
    • Select 1 hour
    • Duration: Indefinitely
  6. Click ok

That’s how you manually setup a Scheduled Task for PowerShell.

Powershell Method

Now we can do a Scheduled Tasks with PowerShell. We will be using the scheduledtask commands to create the task accordingly. Lets take a look at the script itself.

The script – Scheduled Tasks with PowerShell

# Variables
$ScriptPath = "C:\temp\AuditDisabledAccounts.ps1"
$TaskName = "Audit Disabled Accounts"
$OutCSVfile = "C:\Reports\DisabledAccountsAudit.csv"
$Servers = "AD1,AD2,AD3"
$HoursBack = 1
$User = Read-Host -Prompt "Domain\Username"
$Creds = Read-Host -AsSecureString -Prompt "Enter Password" 

$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Creds)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)


$triggers = 0..23 | ForEach-Object {
    New-ScheduledTaskTrigger -At "$($_):00" -Daily
}


$principal = New-ScheduledTaskPrincipal `
    -id 'Author' `
    -UserId "$User" `
    -LogonType Password `
    -RunLevel Limited
    

$Action = New-ScheduledTaskAction `
    -Execute "PowerShell" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`" -HoursBack $HoursBack -Servers $Servers -OutCSVfile `"$OutCSVfile`"" `
    -WorkingDirectory 'C:\temp\'

$Task = New-ScheduledTask `
    -Description 'Usered To Audit Disabled Accounts' `
    -Action $Action `
    -Principal $principal `
    -Trigger $triggers

Register-ScheduledTask `
    -TaskName "$TaskName" `
    -TaskPath '\' `
    -Action $Action `
    -Trigger $triggers `
    -User $User `
    -Password "$UnsecurePassword"

The breakdown

The first thing we do is setup. We want to have the script, the name, the out file for our audit report, our servers, and the hours back we want to go.

Veriables

# Variables
$ScriptPath = "C:\temp\AuditDisabledAccounts.ps1"
$TaskName = "Audit Disabled Accounts"
$OutCSVfile = "C:\Reports\DisabledAccountsAudit.csv"
$Servers = "AD1,AD2,AD3"
$HoursBack = 1
$User = Read-Host -Prompt "Domain\Username"
$Creds = Read-Host -AsSecureString -Prompt "Enter Password" 

The first thing we want is the veriables. We want the path of the script. We want it’s name. Where our CSV files will be dropped, the servers, how many hours back, usernames and passwords. Notice that the User is using a read-host and creds is using a secure string. This is to help stop shoulder surfers and powershell memory. This way you password isn’t passed around. Basicly, we input the password as a secure string, and it becomes a veraible. Thus, if someone is looking through the powershell history, or is monitoring it with something like defender, then they will not see the password. Only the veraible from this point on.

Decoding Passwords as Veriables

Part of the Scheduled Tasks with PowerShell is we need to register the task later. This means that the password needs to be plain text. However, we don’t want a password to ever exist in the shell visability. So we want to decode it directly into a Veriable.

$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Creds)
$UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)

The code above allows you to convert the secure string to normal text in Powershell 5. If you are using powershell 7, this isn’t a problem. But most servers are still defaulting at 5. The new veriable name is UnsecurePassword which has the password as plain text for the register command.

Triggers – Scheduled Task for powershell

We need to start making the triggers. Unlike the gui, we can’t setup a startup with a hourly repeat. Instead, the safeist way is to do an hourly thing for repeating the hour. We do this using the new-scheduledtasktrigger command.

$triggers = 0..23 | ForEach-Object {
    New-ScheduledTaskTrigger -At "$($_):00" -Daily
}

Since we have 24 hours in a day, we want to repeate a foreach-object loop 24 times. We start at 0 and go to 23 which makes 24. Wow… Anyways, As we loop, the $_ will be the number. So we create a new trigger at that time and set it to daily. All of this will be dumped into the $triggers array.

Principal

Next we want to setup a user account. The command for this is…. Yep, you guessed it, New-ScheduledTaskPrincipal. Here we are setting the ID to the author, using our user flag, doing the logontype as password, and the runlevel is limited. We don’t want it to have full access to anything since it’s not doing anything on the local PC. Notice the ` symbol. This allows you to do mulitlple lines with one command. It’s like break here and continue to the next line. It makes reading code so much easier.

$principal = New-ScheduledTaskPrincipal `
    -id 'Author' `
    -UserId "$User" `
    -LogonType Password `
    -RunLevel Limited

Actions

Next we need to do our actions. AKA, what’s it going to do. Using the New-scheduledTaskAction we want to execute with powershell and push our arguments in. Using our Veriables, we fill in the blanks. It’s very straight forward. The secret sause here is the arguments will be like you did with the gui approach.

$Action = New-ScheduledTaskAction `
    -Execute "PowerShell" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`" -HoursBack $HoursBack -Servers $Servers -OutCSVfile `"$OutCSVfile`"" `
    -WorkingDirectory 'C:\temp\'

Tasks

Next we need to make the task itself. We are going to use the New-ScheduledTask command. This part of the command creates a task object that will need to be registered. We give it the description we want. The Actions from above. The user inside the principal names and the triggers we built out.

$Task = New-ScheduledTask `
    -Description 'Usered To Audit Disabled Accounts' `
    -Action $Action `
    -Principal $principal `
    -Trigger $triggers

Register The Task

Finally, we want to register the task in question. We are going to use “Register-scheduledTask” to do this. Notice that this is where we are using that password we used at the start. It’s used as a variable, and thus it’s never shown in the PowerShell history.

Register-ScheduledTask `
    -TaskName "$TaskName" `
    -TaskPath '\' `
    -Action $Action `
    -Trigger $triggers `
    -User $User `
    -Password "$UnsecurePassword"

Additional Thoughts on Scheduled Tasks with PowerShell

This technique is very powerful. I built out a script that scanned the local network via Get-NetNeighbor. The script was a scheduled task and it grabbed all the devices. Imagine having admin rights, pushing out a script that scans the local network drops a scheduled task on another computer that scans that network. You could map out a whole network within a few minutes. This could be used as a worm and it’s a good reason to block WMI on the network except from the machines that does the administration.

What can we learn as a person?

It’s always a good idea to have routine. Having a Scheduled task in your life that you like tends to improve our lives. For example, I like going to a monthly meetup with my friends. It’s something I look forward to. Having it on my calendar helps. This is why vacations are important. We need to have those things on our calendar. It’s ok to have them on the calendar. So, find something you can look forward to, and put it on the calendar.

Additional Resources