On a default Windows installation there is a number of Windows services and scheduled tasks. These are configured to run with a specified set of credentials like LocalSystem, LocalService or NetworkService. New services is typically created when installing applications, which in a domain environment often require a domain account configured as the run as account. The same requirement is true for scheduled tasks, in example a scheduled script which requires specific credentials.
A good practice is keeping track of these run as accounts, as they could potentially be a security threat. For instance a bad practice would be to configure a service or scheduled task to run as an Administrator account, when there is no need for it to run with administrative privileges.
In order to gather information about run as accounts for services and scheduled tasks I`ve created an advanced function in Windows PowerShell, named Get-RunAsAccount. The function uses Windows Management Instrumentation (WMI) to gather information about services, and schtasks.exe to gather information about scheduled tasks.
The Get-ScheduledTask function, which is a wrapper function around schtasks.exe, is created by PowerShell MVP Claus Nielsen. A two part series about managing scheduled tasks is available on the PowerShell Magazine:
- Managing scheduled tasks in your environment – Part I
- Managing scheduled tasks in your environment – Part II
The article series also provides a function for changing the username and password for a scheduled task.
Get-RunAsAccount
More than one item may be specified on the –Computername parameter, for example “Computer-A, computer-B”, thus we use a foreach construct to loop through to the $computername variable.
PROCESS {
foreach ($computer in $computername) {
Get-RunAsAccountWorker -computername $computer
}
}
Next, we set up basic error handling by using a try/catch block, where we start by sending a ping request using Test-Connection, and attempting a connection to the target computer using WMI.
try {
if ((Test-Connection -ComputerName $computername -Count 1 -Quiet) -and ($computersystem = Get-WmiObject -Class win32_computersystem -Computername $computername -ErrorAction stop)) {
Write-Verbose "Connected to computer $computername"
$connectivity = "Success"
$output = @()
If the Test-Connection cmdlet fails, the “else” script block on line 116 will be executed, populating a hash-table for the output variable where the Connectivity property is set to “Failed (ping)”. If the WMI connection fails, an exception is generated and the code in the catch block on line 132 is executed. If the connection succeed we write a message which will be shown if the –Verbose parameter is specified, informing the user that we successfully connected to the computer.
We populate the $connectivity variable which will be used later on, and we set up an empty array for the matching services and scheduled tasks found on the target computer.
Next we gather all services on the computer. Notice that we specify a filter for Get-WmiObject, which performs a wildcard match using the provided RunAsUser parameter value. If the parameter isn`t specified, all services will be returned.
$services = Get-WmiObject win32_service -filter "(StartName Like '%$runasuser%')" -ComputerName $computername -ErrorAction Stop | Select-Object name,startname
if ($services) {
foreach ($service in $services) {
Write-Verbose "Processing service $($service.name)"
$outputservice = @{}
$outputservice.Computername = $computersystem.name
$outputservice.Connectivity = "Success"
$outputservice.Type = "Service"
$outputservice.Name = $service.name
$outputservice.RunAsAccount = $service.startname
$output += $outputservice
}
}
If any services is found, a foreach construct will loop through all the services found, generate a hash-table with information about the service and add it to the $output variable created earlier. The same procedure is then repeated for gathering information about scheduled tasks, using Claus` Get-ScheduledTask function. One difference is that we first check if the RunAsUser parameter is specified, and then run the Get-ScheduledTask function with the correct parameters:
if ($RunAsUser) {
$tasks = Get-ScheduledTask -ComputerName $computername -RunAsUser $RunAsUser | Select-Object taskname,runasuser
}
else {
$tasks = Get-ScheduledTask -ComputerName $computername | Select-Object taskname,runasuser
}
if ($tasks) {
foreach ($task in $tasks) {
Write-Verbose "Processing task $($task.taskname)"
$outputtask = @{}
$outputtask.Computername = $computersystem.name
$outputtask.Connectivity = "Success"
$outputtask.Type = "ScheduledTask"
$outputtask.Name = $task.taskname
$outputtask.RunAsAccount = $task.runasuser
$output += $outputtask
}
}
In the end we output the gathered information. We first check if there is any information populated in the $output variable, as this might be empty if the RunAsAccount parameter is specified, and no services or scheduled tasks running as the specified account is found. If any information is found, we do a foreach loop to output each entry (a hash-table containing either service or task information) to the pipeline:
if ($output) {
foreach ($ht in $output) {
New-Object -TypeName PSObject -Property $ht
}
}
else {
$outputinfo = @{}
$outputinfo.Computername = $($computername)
$outputinfo.Connectivity = "Success"
$outputinfo.Type = $null
$outputinfo.Name = $null
$outputinfo.RunAsAccount = $null
New-Object -TypeName PSObject -Property $outputinfo
}
If the $output variable is empty we create a single object which indicates that we have successfully connected to the computer, but not found any matching services or scheduled tasks.
As a general recommendation in PowerShell; Always output the information as objects. This leaves it up to the consumer of the script or function to decide what to do next. For example he or she might decide to insert the information to a SQL-table, generate an HTML report or export the information to a CSV-file.
We will end the walkthrough by looking at some example usage:
We start by dot-sourcing the function into our current PowerShell session. There is a number of ways to define the function, have a look at the bottom of this article for more information.
Next we run the function without specifying a computer name, which means it will run on the local computer:
In the example above we specified “demo” as the value for the RunAsUser parameter. In this case “demo” is the name of the Active Directory domain, and thus all services and scheduled tasks running under a domain account will be returned.
In the next example the input for the computername parameter is gathered from a text-file, and the RunAsUser we`re looking for is “administrator”:
In the last example we`re retrieving all computer accounts in Active Directory which has the word “server” as part of the OperatingSystem property. We`re then “renaming” the name property of the computer accounts to “computername” using Select-Object, which in turn will make Get-RunAsAccount able to bind to the computername parameter when we pipe all the computer account objects to it:
We could also choose to export the information to a comma separated file:
Which in turn can be opened and customized in Microsoft Office Excel:
You can download the function from here. Feel free to leave a comment if you encounter a bug or have suggestions for improvement.