Link to home
Start Free TrialLog in
Avatar of Albert Widjaja
Albert WidjajaFlag for Australia

asked on

Please help to combine two PowerShell scripts for Computer service account to work properly ?

People,

I need some assistance to modify the below script so it runs with specific filtering on certain OU:

<#
Created by: https://www.experts-exchange.com/members/oBdA.html
.SYNOPSIS
Gets a list of accounts used in services and tasks.
.DESCRIPTION
Gets a list of accounts used in services and tasks. Default Windows accounts will be filtered out.
.PARAMETER ComputerName
A list of computer names to query.
You can pipe strings to this argument.
.PARAMETER Filter
A list of strings with account names to look for; overrides the default filter.
Note that accounts may show up as DnsDomainName\User, NetBiosDomainName\User, or user@DnsDomainName.
.PARAMETER ServiceOnly
Process only services.
.PARAMETER TaskOnly
Process only tasks.
.INPUTS
System.String
.OUTPUTS
System.Management.Automation.PSCustomObject
.EXAMPLE
Get-ServiceOrTaskAccount
.EXAMPLE
Get-ServiceOrTaskAccount -ComputerName SomeMachine, SomeOtherMachine -ServiceOnly
.EXAMPLE
Get-Content .\servers.txt | Get-ServiceOrTaskAccount -Filter "Domain\Administrator", "Administrator@domain.com"
#>
#requires -Version 3
[CmdletBinding(DefaultParameterSetName="Get_Service_Task")]
Param(
	[Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$True)]
	[string[]]$ComputerName = @($ENV:ComputerName),
	[string[]]$Filter,
	[Parameter(Mandatory=$false, ParameterSetName="Get_Service")]
	[switch]$ServiceOnly,
	[Parameter(Mandatory=$false, ParameterSetName="Get_Task")]
	[switch]$TaskOnly
)
Begin {
	$IgnoreAccounts = @()
	ForEach ($Name In ('NT AUTHORITY\LocalService', 'NT AUTHORITY\LocalSystem', 'NT Authority\NetworkService')) {
		$IgnoreAccounts += $Name
		$IgnoreAccounts += $Name.Split('\')
	}
	ForEach ($Sid In ('S-1-5-18', 'S-1-5-19', 'S-1-5-20', 'S-1-5-80-0')) {
		$PrincipalSID = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $Sid
		$Principal = $PrincipalSID.Translate([System.Security.Principal.NTAccount])
		$IgnoreAccounts += $Sid
		$IgnoreAccounts += $Principal.Value
		$IgnoreAccounts += $Principal.Value.Split('\')
	}
	$IgnoreAccounts = $IgnoreAccounts | Sort-Object -Unique
	$Result = [ordered]@{
		'ComputerName' = $Null
		'Type' = $Null
		'Name' = $Null
		'Account' = $Null
		'Exception' = $Null
	}
	If ($Filter) {
		Write-Warning "Will only list objects running with the following accounts:`r`n'$($Filter -join "', '")'"
	} Else {
		Write-Warning "Will list objects NOT running with the following accounts:`r`n'$($IgnoreAccounts -join "', '")'"
	}
	
	Function Select-Account([System.Collections.Specialized.OrderedDictionary]$Result) {
		$Skip = $False
		If ($Filter) {
			If ($Filter -notcontains $Result['Account']) {
				$Skip = $True
			}
		} Else {
			If ($IgnoreAccounts -contains $Result['Account']) {
				$Skip = $True
			}
		}
		If ($Skip) {
			Write-Verbose -Message "[$($Result['ComputerName'])] Ignored $($Result['Type']) '$($Result['Name'])', account '$($Result['Account'])'."
		} Else {
			New-Object -TypeName PSObject -Property $Result
		}
	}
}
Process {
	ForEach ($Computer In $ComputerName) {
		$Result['ComputerName'] = $Computer
		If ($PsCmdlet.ParameterSetName.Contains('Service')) {
			$Result['Type'] = 'Service'
			Try {
				ForEach ($Service In (Get-WmiObject -Query "Select DisplayName, StartName From Win32_Service" -ComputerName $Computer -ErrorAction Stop)) {
					$Result['Name'] = $Service.DisplayName
					$Result['Account'] = $Service.StartName
					Select-Account -Result $Result
				}
			} Catch {
				$Result['Name'] = $Null
				$Result['Account'] = $Null
				$Result['Exception'] = $_.Exception.Message
				New-Object -TypeName PSObject -Property $Result
				$Result['Exception'] = $Null
			}
		}
		If ($PsCmdlet.ParameterSetName.Contains('Task')) {
			$Result['Type'] = 'Task'
			$Output = schtasks.exe /s $Computer /xml ONE 2>&1
			If ($LastExitCode -eq 0) {
				[xml]$xml = $Output -replace 'xmlns=', '_xmlns='
				ForEach ($Node In $xml.SelectNodes("Tasks/Task/Principals/Principal/UserId")) {
					$Result['Name'] = $Node.SelectSingleNode('../../../preceding-sibling::comment()[1]').InnerText.Trim()
					$Result['Account'] = $Node.'#text'
					Select-Account -Result $Result
				}
			} Else {
				$Result['Name'] = $Null
				$Result['Account'] = $Null
				$Result['Exception'] = $Output -join " "
				New-Object -TypeName PSObject -Property $Result
				$Result['Exception'] = $Null
			}
		}
	}
}
End {
}

Open in new window


When you execute this line below:

Get-ADComputer -Properties * -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase "DC=Domain,DC=com" | Where-Object {Test-Connection $_.Name -Count 1 -Quiet} |Select -ExpandProperty Name 

Open in new window


It works and shows the server OS, so how do you combine the two script above to make it running ?

I have tried below but end up in error:

$Servers = Get-ADComputer -Properties * -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase "DC=domain,DC=com" | Where-Object {Test-Connection $_.Name -Count 1 -Quiet} 

$Servers | 'C:\temp\Get-ServiceOrTaskAccount.ps1' -Filter "Domain\Admin*", "Admin*@domain.com" | Export-CSV -Path C:\Result.CSV -NTI 

Open in new window


Error: Expressions are only allowed as the first element of a pipeline.
Avatar of David Johnson, CD
David Johnson, CD
Flag of Canada image

Clear-Host
$searchbase = 'DC=example,DC=com' 
$Servers = get-adcomputer -Filter  {Enabled -eq $true -and OperatingSystem -like "*Server*"} -SearchBase $searchbase | select-object -ExpandProperty Name
$results = $servers | c:\temp\Get-ServiceAccounts.ps1  
$results | Export-Csv -NoTypeInformation -Path c:\temp\results.csv

Open in new window

Avatar of Albert Widjaja

ASKER

David, thanks for the quick reply, so how about the -Filter "Domain\Admin*", "Admin*@domain.com" parameter ?
is it possible to be fitted in the script as you suggested ?
just add it as a parameter
Somehow it is still not working like the below error:

Get-ADComputer : The server has returned the following error: invalid enumeration context.
At line:4 char:12
+ $Servers = Get-ADComputer -Filter {Enabled -eq $True -and OperatingSy ...
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-ADComputer], ADException
    + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADComputer
 
C:\Scripts\Get-ServiceAccounts.ps1 : The term 'C:\Scripts\Get-ServiceAccounts.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:5 char:23
+ $results = $servers | C:\Scripts\Get-ServiceAccounts.ps1
+                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Scripts\Get-ServiceAccounts.ps1:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
 
Export-Csv : Cannot bind argument to parameter 'InputObject' because it is null.
At line:6 char:12
+ $results | Export-Csv -NoTypeInformation -Path c:\Scripts\results.csv
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Export-Csv], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ExportCsvCommand

I have placed the script in the same C:\Scripts\ directory but it is still not working ?
I don't know.. perhaps you need to hard code it
Avatar of oBdA
oBdA

Your first command was pretty close.
The main issue is that you enclosed the path to the script in quotes (which isn't a bad thing per se), which turns it into a string, as far as PowerShell is concerned. Since you want to execute it, you need to use the "call" operator "&"
Second issue is that the script doesn't handle wildcards, so you'll have to list out the admin accounts you're looking for
$Servers = Get-ADComputer -Properties * -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase "DC=domain,DC=com" | Where-Object {Test-Connection $_.Name -Count 1 -Quiet} 
$Servers | & 'C:\temp\Get-ServiceOrTaskAccount.ps1' -Filter "Domain\Admin", "Admin@domain.com" | Export-CSV -Path C:\Result.CSV -NTI  

Open in new window

Cool,
OBDA, can I put the two lines you suggested into the same Script file ?
Let say, at the bottom of the main script file.
It's PowerShell, of course you can. Just save them as Whatever.ps1
Or do you want to combine the Get-ServiceOrTaskAccount.ps1 and the AD query into one single script?
Yes, that's what I meant.
You wrap the current script into a function, and then just call the function:
Function Get-ServiceOrTaskAccount {
<#
.SYNOPSIS
Gets a list of accounts used in services and tasks.
.DESCRIPTION
Gets a list of accounts used in services and tasks. Default Windows accounts will be filtered out.
.PARAMETER ComputerName
A list of computer names to query.
You can pipe strings to this argument.
.PARAMETER Filter
A list of strings with account names to look for; overrides the default filter.
Note that accounts may show up as DnsDomainName\User, NetBiosDomainName\User, or user@DnsDomainName.
.PARAMETER ServiceOnly
Process only services.
.PARAMETER TaskOnly
Process only tasks.
.INPUTS
System.String
.OUTPUTS
System.Management.Automation.PSCustomObject
.EXAMPLE
Get-ServiceOrTaskAccount
.EXAMPLE
Get-ServiceOrTaskAccount -ComputerName SomeMachine, SomeOtherMachine -ServiceOnly
.EXAMPLE
Get-Content .\servers.txt | Get-ServiceOrTaskAccount -Filter "Domain\Administrator", "Administrator@domain.com"
.NOTES
Created by: https://www.experts-exchange.com/members/oBdA.html
#>
#requires -Version 3
[CmdletBinding(DefaultParameterSetName="Get_Service_Task")]
Param(
	[Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$True)]
	[string[]]$ComputerName = @($ENV:ComputerName),
	[string[]]$Filter,
	[Parameter(Mandatory=$false, ParameterSetName="Get_Service")]
	[switch]$ServiceOnly,
	[Parameter(Mandatory=$false, ParameterSetName="Get_Task")]
	[switch]$TaskOnly
)
	Begin {
		$IgnoreAccounts = @()
		ForEach ($Name In ('NT AUTHORITY\LocalService', 'NT AUTHORITY\LocalSystem', 'NT Authority\NetworkService')) {
			$IgnoreAccounts += $Name
			$IgnoreAccounts += $Name.Split('\')
		}
		ForEach ($Sid In ('S-1-5-18', 'S-1-5-19', 'S-1-5-20', 'S-1-5-80-0')) {
			$PrincipalSID = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $Sid
			$Principal = $PrincipalSID.Translate([System.Security.Principal.NTAccount])
			$IgnoreAccounts += $Sid
			$IgnoreAccounts += $Principal.Value
			$IgnoreAccounts += $Principal.Value.Split('\')
		}
		$IgnoreAccounts = $IgnoreAccounts | Sort-Object -Unique
		$Result = [ordered]@{
			'ComputerName' = $Null
			'Type' = $Null
			'Name' = $Null
			'Account' = $Null
			'Exception' = $Null
		}
		If ($Filter) {
			Write-Warning "Will only list objects running with the following accounts:`r`n'$($Filter -join "', '")'"
		} Else {
			Write-Warning "Will list objects NOT running with the following accounts:`r`n'$($IgnoreAccounts -join "', '")'"
		}
		
		Function Select-Account([System.Collections.Specialized.OrderedDictionary]$Result) {
			$Skip = $False
			If ($Filter) {
				If ($Filter -notcontains $Result['Account']) {
					$Skip = $True
				}
			} Else {
				If ($IgnoreAccounts -contains $Result['Account']) {
					$Skip = $True
				}
			}
			If ($Skip) {
				Write-Verbose -Message "[$($Result['ComputerName'])] Ignored $($Result['Type']) '$($Result['Name'])', account '$($Result['Account'])'."
			} Else {
				New-Object -TypeName PSObject -Property $Result
			}
		}
	}
	Process {
		ForEach ($Computer In $ComputerName) {
			$Result['ComputerName'] = $Computer
			If ($PsCmdlet.ParameterSetName.Contains('Service')) {
				$Result['Type'] = 'Service'
				Try {
					ForEach ($Service In (Get-WmiObject -Query "Select DisplayName, StartName From Win32_Service" -ComputerName $Computer -ErrorAction Stop)) {
						$Result['Name'] = $Service.DisplayName
						$Result['Account'] = $Service.StartName
						Select-Account -Result $Result
					}
				} Catch {
					$Result['Name'] = $Null
					$Result['Account'] = $Null
					$Result['Exception'] = $_.Exception.Message
					New-Object -TypeName PSObject -Property $Result
					$Result['Exception'] = $Null
				}
			}
			If ($PsCmdlet.ParameterSetName.Contains('Task')) {
				$Result['Type'] = 'Task'
				$Output = schtasks.exe /s $Computer /xml ONE 2>&1
				If ($LastExitCode -eq 0) {
					[xml]$xml = $Output -replace 'xmlns=', '_xmlns='
					ForEach ($Node In $xml.SelectNodes("Tasks/Task/Principals/Principal/UserId")) {
						$Result['Name'] = $Node.SelectSingleNode('../../../preceding-sibling::comment()[1]').InnerText.Trim()
						$Result['Account'] = $Node.'#text'
						Select-Account -Result $Result
					}
				} Else {
					$Result['Name'] = $Null
					$Result['Account'] = $Null
					$Result['Exception'] = $Output -join " "
					New-Object -TypeName PSObject -Property $Result
					$Result['Exception'] = $Null
				}
			}
		}
	}
	End {
	}
}

Get-ADComputer -Properties * -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase "DC=domain,DC=com" |
	Where-Object {Test-Connection $_.Name -Count 1 -Quiet} |
	Get-ServiceOrTaskAccount -Filter "Domain\Admin", "Admin@domain.com" |
	Export-Csv -NoTypeInformation -Path C:\Result.CSV

Open in new window

Obda,

Thanks for the reply, I've customized it like the below:

$LogfilePath = "C:\Result.CSV"

$OUList = @(
	"OU=Site 1,DC=Domain,DC=com"
	"OU=Site 2,DC=Domain,DC=com"
)

$UserAccounts = @(
    "Domain\Administrator"
    "Administrator@Domain.com"
)

$OUList | ForEach {
    $OU = $_
    Get-ADComputer -Properties * -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU -SearchScope Subtree |
	    Where-Object {Test-Connection $_.Name -Count 1 -Quiet} |
	    Get-ServiceOrTaskAccount -Filter @UserAccounts |
	    Export-CSV -Path $LogfilePath -NoTypeInformation
}

Write-Host "Done; results written to '$($LogfilePath)'." -ForegroundColor White

Open in new window


This is the error:
Get-ServiceOrTaskAccount : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do 
not match any of the parameters that take pipeline input.
At C:\Get-ServiceOrTaskAccount.ps1:146 char:6
+         Get-ServiceOrTaskAccount -Filter @UserAccounts |
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (CN=PRDMAIL01-VM,OU=...BS,DC=com,:PSObject) [Get-ServiceOrTaskAccount], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectNotBound,Get-ServiceOrTaskAccount

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of oBdA
oBdA

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Many thanks for the sharing and assistance in this matter ObdA.
It works.

Despite some weird error like in the result attached.
result.csv