Optimize slow powershell script - powershell

I wrote this script and it works but its painfully slow, can you please point out why ? and provide some ideas on how to optimize its functionality. in can make simple Powershell scrips however I have a very had time looking up methodology on google not knowing what to look for
my script
$i=1;
foreach ($PC in $ComputerName) {
$per = ($i/$ComputerName.Length)*100
try {
# Get-ADComputer $pcs -properties name,enabled | select-object name,enabled
$status = Get-ADComputer -Identity $PC -Properties Enabled | select-object -ExpandProperty Enabled
if(Test-Connection -ComputerName $PC -Quiet -Count 1){
$quserOut = quser.exe /SERVER:$PC 2>&1
if ($quserOut -match "No user exists"){
"$PC>On Line>$status>No users loggedIn"; continue
}else{
$users = $quserOut -replace '\s{2,}', ',' |
ConvertFrom-CSV -Header 'username', 'sessionname', 'id', 'state', 'idleTime', 'logonTime' |
Add-Member -MemberType NoteProperty -Name ComputerName -Value $PC -PassThru
$users = $users[1..$users.count]
for ($i = 0; $i -lt $users.count; $i++){
if ($users[$i].sessionname -match '^\d+$'){
$users[$i].logonTime = $users[$i].idleTime
$users[$i].idleTime = $users[$i].STATE
$users[$i].STATE = $users[$i].ID
$users[$i].ID = $users[$i].SESSIONNAME
$users[$i].SESSIONNAME = $null
}
}
$users = $users | Sort-Object -Property idleTime
# $status = Get-ADComputer -Identity $PC -Properties Enabled | select-object -ExpandProperty Enabled
$Usr = $users | Where-Object { $_.state -eq 'Active' } | select-object -ExpandProperty username
"$PC>On Line>$status>$Usr"
}
} else {
"$PC>Not Online>$status>NoUserDataRetrieve"
}
}
catch {
"$PC>Not in AD>$status>NoUserDataRetrieve"
}
Write-Progress -Activity "Procesando Usuarios:" -Status "Usuario EN Proceso: $i -- $PC" -PercentComplete $per
Start-Sleep -Milliseconds 100
$i++
}
this displays the following data
basically script test if PC exist in Active Directory, it does a ping test , and gets back current logged in user
Machine Name > Ping Test pass? > AD status > current logged in user
PC1>Online>True>BazVic
PC2>NotOnLine>True>No Available Data
PC3>OnLine>True>ReyesDa
PC2>NotOnLine>FALSE>No Available Data

Related

Selection of only one user from list

I have this script that I need to use to retrieve the data of a particular user "ADTuser" from a list of servers the script works well, but the output file with my user add also other users' detail that is not needed for my final output how can I filter it to only the user that I need.
get-content C:\servers.txt | foreach-object {
$Comp = $_
if (test-connection -computername $Comp -count 1 -quiet) {
([ADSI]"WinNT://$comp").Children | ?{$_.SchemaClassName -eq 'user' } | %{
$groups = $_.Groups() | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$_ | Select #{n='Computername';e={$comp}},
#{n='UserName';e={$_.Name}},
#{n='Memberof';e={$groups -join ';'}},
#{n='status'; e={if($groups -like "*Administrators*"){$true} else{$false}}}
}
} Else {Write-Warning "Server '$Comp' is Unreachable hence Could not fetch data"}
} | Out-File -FilePath C:\users.txt
This should be an easier way of doing what you're looking for, Get-CimInstance and Get-CimAssociatedInstance have been around since PowerShell 3:
Get-Content C:\servers.txt | ForEach-Object {
$computer = $_
try {
$query = Get-CimInstance Win32_UserAccount -Filter "Name='ADTuser'" -ComputerName $_ -ErrorAction Stop
foreach($object in $query) {
$membership = Get-CimAssociatedInstance -InputObject $object -ResultClassName Win32_Group -ComputerName $_
[pscustomobject]#{
Computername = $_
UserName = $object.Name
Memberof = $membership.Name -join ';'
Status = $membership.Name -contains 'Administrators'
}
}
}
catch {
Write-Warning "Server '$computer' is Unreachable hence Could not fetch data"
}
} | Export-Csv C:\users.csv -NoTypeInformation
If that doesn't work for you, your code would require a simple modification on your first filtering statement:
Where-Object { $_.SchemaClassName -eq 'user' -and $_.Name.Value -eq 'ADTuser' }
It's important to note that Test-Connection -ComputerName $_ -Count 1 -Quiet is not a relevant test for this script, this command is testing for ICMP response and adsi over WinNT requires RPC connectivity as well SMB.
Putting it all together with minor improvements the script would look like this:
Get-Content C:\servers.txt | ForEach-Object {
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
Write-Warning "Server '$_' is Unreachable hence Could not fetch data"
return
}
$computer = $_
([adsi]"WinNT://$_").Children.ForEach{
if($_.SchemaClassName -ne 'user' -and $_.Name.Value -ne 'ADTuser') {
return
}
$groups = $_.Groups().ForEach([adsi]).Name
[pscustomobject]#{
Computername = $computer
UserName = $_.Name.Value
Memberof = $groups -join ';'
Status = $groups -contains 'Administrators'
}
}
} | Export-Csv C:\users.csv -NoTypeInformation

invoke command on all those servers to see which admin accounts are active on each server

I have been fetching both direct members and groups inside local administrators group in our remote machines. I want to get an output like below.
Also , if there are GROUP members inside local admin group then I want to organize only GROUP members like below.
MACHINE01,User01,TRUE,GROUP01;GROUP02
output:
"Computername","Members"
"MACHINE01","contoso\User01 contoso\User02 contoso\GROUP01 contoso\GROUP02
desired output:
Computername,Direct Members,Account Status,Group Members
MACHINE01,User01,TRUE,GROUP01;GROUP02
MACHINE01,User02,FALSE
MACHINE02,User05,TRUE,GROUP04;GROUP05;GROUP12
MACHINE02,User08,FALSE
MACHINE02,User12,FALSE
MACHINE44,User07,TRUE
script :
$server_list = #()
Import-Csv C:\temp\server3.csv | ForEach-Object {$server_list += $_.name}
invoke-command {
$members = net localgroup administrators |
where {$_ -AND $_ -notmatch "command completed successfully"} |
select -skip 4
New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Members=$members
}
} -computer $server_list -HideComputerName | Select * -ExcludeProperty RunspaceID, PSComputerName, PSShowComputerName | Export-CSV c:\temp\local_admins2.csv -NoTypeInformation
Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=test,DC=local" | select
name | Export-Csv C:\data\servers3.csv
Output:
"ComputerName","Direct Members","Account Status","Group Members"
"machine01","user01","OK",""
"machine01","user02","Degraded",""
"machine02","user03","OK",""
"machine02","user04","Degraded",""
LASTLY UPDATE OUTPUT WMI:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
**MACHINE01 Administrator Degraded Domain Admins;IT-Admins**
MACHINE01 Theo OK
MACHINE01 LocalAdmin OK
**MACHINE02 Administrator Degraded DBA Admins;Software-Admins**
MACHINE02 Theo OK
MACHINE02 LocalAdmin OK
net localgroup does not output anything that differentiates between users or groups, so if possible, use
Get-LocalGroupMember and Get-LocalUser:
$server_list = (Import-Csv -Path 'C:\temp\server3.csv').name
$result = Invoke-Command -ComputerName $server_list -ScriptBlock {
$members = Get-LocalGroupMember -Group "Administrators"
$groups = $members | Where-Object {$_.ObjectClass -eq 'Group'} | ForEach-Object {($_.Name -split '\\')[-1]}
$users = #($members | Where-Object {$_.ObjectClass -eq 'User'})
if ($users.Count) {
# now loop over the user objects
$users | ForEach-Object {
$name = ($_.Name -split '\\')[-1]
$user = if ($_.PrincipalSource -eq 'Local') {
Get-LocalUser -Name $name -ErrorAction SilentlyContinue
}
else {
Get-ADUser -Filter "SamAccountName -eq '$name'" -ErrorAction SilentlyContinue
}
# output an object
[PsCustomObject]#{
'ComputerName' = $env:COMPUTERNAME
'Direct Members' = $name
'Account Status' = $user.Enabled
'Group Members' = $groups -join ';'
}
# clear the $goups here because you only want to list them once per server
$groups = $null
}
}
else {
# no users, just groups
[PsCustomObject]#{
'ComputerName' = $env:COMPUTERNAME
'Direct Members' = $null
'Account Status' = $null
'Group Members' = $groups -join ';'
}
}
} | Select-Object * -ExcludeProperty PSComputerName, RunspaceId
$result | Export-Csv -Path 'c:\temp\local_admins2.csv' -NoTypeInformation
If module Microsoft.Powershell.LocalAccounts is not available to you, you can experiment with module localaccount, but I have no experience with that..
I don't have that old OSes, so you'll have to test this.
Below uses WMI to query the servers for the group memberships:
$server_list = (Import-Csv -Path 'C:\temp\server3.csv').name
$result = foreach ($server in $server_list) {
$query = "Associators of {Win32_Group.Domain='$server',Name='Administrators'} where Role=GroupComponent"
$members = Get-WmiObject -Query $query -ComputerName $server |
Where-Object { $_.__CLASS -match '(User|Group)' } |
Select-Object Name, Caption,
#{Name = 'ObjectClass'; Expression = {$matches[1]}},
#{Name = 'ComputerName'; Expression = {$_.__SERVER}},
Status, LocalAccount, SID, Domain
$groups = #($members | Where-Object {$_.ObjectClass -eq 'Group'})
$users = #($members | Where-Object {$_.ObjectClass -eq 'User'})
if ($users.Count) {
# now loop over the user objects
$users | ForEach-Object {
# output an object
[PsCustomObject]#{
'ComputerName' = $_.ComputerName
'Direct Members' = $_.Name
'Account Status' = $_.Status
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
# clear the $groups here because you only want to list them once per server
$groups = $null
}
}
elseif ($groups.Count) {
# no users, just groups
[PsCustomObject]#{
'ComputerName' = $groups[0].ComputerName
'Direct Members' = $null
'Account Status' = $null
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
}
}
$result | Export-Csv -Path 'c:\temp\local_admins2.csv' -NoTypeInformation
Another alternbative is to use [ADSI]:
$server_list = (Import-Csv -Path 'C:\temp\server3.csv').name
$group = 'Administrators'
$members = foreach ($server in $server_list) {
try {
([ADSI]"WinNT://$server/$group,group").psbase.Invoke('Members') | ForEach-Object {
# test if local or domain
$ADSPath = $_.GetType().InvokeMember("ADSPath", 'GetProperty', $null, $_, $null)
$local = ($ADSPath -like 'WinNT://*')
# get the object name
$name = $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
# get object class
$class = $_.GetType().InvokeMember('Class', 'GetProperty', $null, $_, $null)
if ($class -eq 'User') {
if ($local) {
$flag = $_.GetType().InvokeMember("userflags", 'GetProperty', $null, $_, $null)
$enabled = !($flag -band 2) # ADS_UF_ACCOUNTDISABLE
}
else {
$enabled = (Get-ADUser -Filter "SamAccountName -eq '$name'" -ErrorAction SilentlyContinue).Enabled
}
}
else { $enabled = $null }
[PSCustomObject] #{
ComputerName = $server.ToUpper()
Group = $group
Name = $name
ObjectClass = $class
Enabled = $enabled
}
}
}
catch {
Write-Warning $_
}
}
$groups = #($members | Where-Object {$_.ObjectClass -eq 'Group'})
$users = #($members | Where-Object {$_.ObjectClass -eq 'User'})
$result = if ($users.Count) {
# now loop over the user objects
$users | ForEach-Object {
# output an object
[PsCustomObject]#{
'ComputerName' = $_.ComputerName
'Direct Members' = $_.Name
'Account Status' = $_.Enabled
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
# clear the $groups here because you only want to list them once per server
$groups = $null
}
}
elseif ($groups.Count) {
# no users, just groups
[PsCustomObject]#{
'ComputerName' = $_.ComputerName
'Direct Members' = $null
'Account Status' = $null
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
}
$result | Export-Csv -Path 'c:\temp\local_admins2.csv' -NoTypeInformation
Output from the above when testing
Result using Get-LocalGroupMember:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
MACHINE01 Administrator False Domain Admins;IT-Admins
MACHINE01 LocalAdmin True
MACHINE01 Theo True
Result using WMI:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
MACHINE01 Administrator Degraded Domain Admins;IT-Admins
MACHINE01 Theo OK
MACHINE01 LocalAdmin OK
Result using ADSI:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
MACHINE01 Administrator False Domain Admins;IT-Admins
MACHINE01 Theo True
MACHINE01 LocalAdmin True

Powershell memory usage in runspacepool

i have a problem with code:
Measure-Command{ $controller=Get-ADDomainController -Filter *| Select -ExpandProperty Hostname
$users=Get-ADUser -Filter * |select samaccountname
$scriptblock={
param($samacc,$controller)
$result=#()
foreach($cont in $controller){
$RESULT=$result + (Get-ADUser -Server $cont -Identity $samacc -Properties lastlogon,whenchanged,displayname,title,company | sort-object lastLogon -descending | select-object enabled,displayname,samaccountname,title,company, #{Name="lastLogon";Expression={[datetime]::FromFileTime($_.'lastLogon')}},whenchanged)
}
$result|Sort-Object -Descending -Property LastLogon|select -First 1
}
$MaxThreads = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
$RunspacePool.ApartmentState = "MTA"
$job=#()
$RunspacePool.open()
foreach($user in $users){
$PowerShell = [powershell]::Create().AddScript($scriptblock).AddArgument($user.samaccountname).AddArgument($controller)
$PowerShell.RunspacePool = $RunspacePool
$job+=[PSCustomObject]#{
Id = $_
Pipe = $PowerShell
Handle = $PowerShell.BeginInvoke()
Object = $Object
}
}
while ($job.Handle -ne $null){
$Completed = $job | Where-Object { $_.Handle.IsCompleted -eq $true }
foreach ($Runspace in $Completed){
$data=$Runspace.Pipe.EndInvoke($Runspace.Handle)
$data|Export-Csv d:\fulllist.csv -Append -Delimiter ';' -Encoding UTF8 -NoTypeInformation
$Runspace.Handle = $null
}
Start-Sleep -Milliseconds 100
}
$PowerShell.Dispose()
$RunspacePool.Dispose()
Remove-Variable controller,users,scriptblock,job,Completed,data,Runspace,RunspacePool,PowerShell
[System.GC]::Collect()
}
I create for each user instance with powershell command, and throw it to runspacepool. But i have about 35000 users and when i reach about 18000 for me start problem with connection\session. And to result table get only data for 22000 users. Powershell then dont free memory. How can i correctly manage close instances to free memory (for users that already write to file).May be i use wrong place for commands or wrong commands.

Modifying PowerShell to export Windows Update to CSV file for specific OU only not working

I need to get the list of the server last Windows Update patch from multiple different OU and then export it as in CSV file with the below column and its sample result I gather manually running Get-HotFix locally on each server:
ServerName, Last Time Update Installed, KB Number, KB Update Name, InstalledBy
PRODSQL01-VM, 31/12/2018 02:46:55, KB4462930, Cumulative Update, NT AUTHORITY\SYSTEM
PRODSQL02-VM, 18/12/2018 12:00:00 AM, KB4471324, Security Update, DOMAIN\SVC_SCCM
PRODDC01-VM, 16/1/2019 02:16:31, KB4343669, Cumulative Update, DOMAIN\SVC_SCCM
PRODDC02-VM, 13/1/2018 03:00:00 AM, KB4457146, Security Update, DOMAIN\Admin-Staff1
This is the modified script for multiple OU processing, but somehow the result is still a blank CSV file:
$CsvFile = 'C:\Result.csv'
$key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$OUList = #(
"OU=TEST Servers,OU=MyDomain Testing,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=MyDomain Sydney,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=New Company,DC=MyDomain,DC=com"
)
$OUList | ForEach-Object {
$OU = $_
$Computers = Get-ADComputer -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU |
Select-Object -ExpandProperty DNSHostName |
ForEach-Object {
If (Test-Connection $_ -Count 1 -Quiet) {
$_
}
Else {
Write-Host "Cannot reach $($_)" -ForegroundColor Red
}
}
ForEach ($computer in $Computers) {
Try {
$remoteBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype, $computer)
$regKey = $remoteBase.OpenSubKey($key)
$keyValue = $regkey.GetValue('LastSuccessTime')
Write-Host ''
Write-Host "$($computer): last time updates were installed was $($keyValue)"
}
Catch {
$ | Write-Error
}
Finally {
If ($regKey) {$regKey.Close()}
}
}
} | Export-Csv -Path $Csvfile -NoTypeInformation
As others have mentioned, you're doing write-host rather than adding anything to your CSV file.
Note that I haven't tested any of the below code - it's just a bit of rearranging.
$CsvFile = 'C:\Result.csv'
$Results = #() #object to hold the output
$key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$OUList = #(
"OU=TEST Servers,OU=MyDomain Testing,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=MyDomain Sydney,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=New Company,DC=MyDomain,DC=com"
)
ForEach ($OU in $OUList) {
Get-ADComputer -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU |
Select-Object -ExpandProperty DNSHostName |
ForEach-Object {
If (Test-Connection $_ -Count 1 -Quiet) {
Try {
$remoteBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype, $_)
$regKey = $remoteBase.OpenSubKey($key)
$keyValue = $regkey.GetValue('LastSuccessTime')
# Add result to Results array. Not Result
$results += [pscustomobject]#{
Computer = $_
LastSuccessTime = $KeyValue
}
}
Catch {
$ | Write-Error
}
Finally {
If ($regKey) {$regKey.Close()}
}
}
Else {
Write-Host "Cannot reach $($_)" -ForegroundColor Red
}
}
}
#export result object to CSV
$Results | Export-Csv -Path $Csvfile -NoTypeInformation

Table not showing output in new line

I'm sure there is a simple solution, but I'm stuck. The output in the members column is like this
{domain\Domain Admins, domain\joerod...
How can I show the
$member
value on each line?
Function Get-AdminGroups{
foreach($i in (Get-Content C:\Users\joerod\Desktop\remove_users.txt)){
#test if machine is on the network
if (-not (Test-Connection -computername $i -count 1 -Quiet -ErrorAction SilentlyContinue)) {
Write-Warning "$i is Unavalible"
"`r"
}
else {
(invoke-command {
$members = net localgroup administrators |
? {$_ -AND $_ -notmatch "command completed successfully"} |
select -skip 4
New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Users=$members
}
} -computer $i -HideComputerName |
Select * -ExcludeProperty RunspaceID )
}
}
}
Get-AdminGroups |ft
Iterate through $members and make an object for each one. This creates an empty array, loops through the computers in your text file, and in that loop it pulls a list of the local administrators, and for each one it creates a custom object just like you are doing, and it adds it to that array.
$Results = #()
foreach($i in (GC C:\Users\joerod\Desktop\remove_users.txt)){
#test if machine is on the network
if (!(Test-Connection -computername $i -count 1 -Quiet -ErrorAction SilentlyContinue)) {
Write-Warning "$i is Unavalible`r"
Continue
}
invoke-command {
$members = net localgroup administrators |?{$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4
ForEach($member in $members){
$Results += New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Users=$member
}
}
} -computer $i -HideComputerName # | Select * -ExcludeProperty RunspaceID
}
$Results | FT