Powershell memory usage in runspacepool - powershell

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.

Related

Script to remove user profiles using powershell

I'm trying to build a powershell script that I can use to delete all or some of the user profiles on multiple pc's since they often cause the drives to go full.
I found the current script which I got to work for me, but I'd like to optimize it so I can input or import a list of computers where I want him to remove all the user profiles from.
Can you guys help me to input this feature?
Current Code:
$ExcludedUsers ="admin","test"
$RunOnServers = $false
[int]$MaximumProfileAge = 0 # Profiles older than this will be deleted
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
if ($RunOnServers -eq $true -or $osInfo.ProductType -eq 1) {
New-EventLog -LogName Application -Source "Stone Profile Cleanup" -ErrorAction SilentlyContinue
$obj = Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special -and $_.Loaded -eq $false )}
#$output = #()
foreach ($littleobj in $obj) {
if (!($ExcludedUsers -like $littleobj.LocalPath.Replace("C:\Users\",""))) {
$lastwritetime = (Get-ChildItem -Path "$($littleobj.localpath)\AppData\Local\Microsoft\Windows\UsrClass.dat" -Force ).LastWriteTime
if ($lastwritetime -lt (Get-Date).AddDays(-$MaximumProfileAge)) {
$littleobj | Remove-WmiObject
# $output += [PSCustomObject]#{
# 'RemovedSID' = $littleobj.SID
# 'LastUseTime' = $litteobj.LastUseTime
# 'LastWriteTime' = $lastwritetime
# 'LocalPath' = $littleobj.LocalPath
# }
}
}
}
#$output | Sort LocalPath | ft
#$output | Sort LocalPath | ft * -AutoSize | Out-String -Width 4096 | Out-File -filepath "C:\MyOutput.TXT" -append -Encoding Unicode
Write-EventLog –LogName Application –Source "Stone Profile Cleanup" –EntryType Information –EventID 1701 -Category 2 -Message ("Profiles older than $MaximumProfileAge days have been cleaned up")
}$ExcludedUsers ="adminbholemans","testbholemans1"
$RunOnServers = $false
[int]$MaximumProfileAge = 0 # Profiles older than this will be deleted
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
if ($RunOnServers -eq $true -or $osInfo.ProductType -eq 1) {
New-EventLog -LogName Application -Source "Stone Profile Cleanup" -ErrorAction SilentlyContinue
$obj = Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special -and $_.Loaded -eq $false )}
#$output = #()
foreach ($littleobj in $obj) {
if (!($ExcludedUsers -like $littleobj.LocalPath.Replace("C:\Users\",""))) {
$lastwritetime = (Get-ChildItem -Path "$($littleobj.localpath)\AppData\Local\Microsoft\Windows\UsrClass.dat" -Force ).LastWriteTime
if ($lastwritetime -lt (Get-Date).AddDays(-$MaximumProfileAge)) {
$littleobj | Remove-WmiObject
# $output += [PSCustomObject]#{
# 'RemovedSID' = $littleobj.SID
# 'LastUseTime' = $litteobj.LastUseTime
# 'LastWriteTime' = $lastwritetime
# 'LocalPath' = $littleobj.LocalPath
# }
}
}
}
#$output | Sort LocalPath | ft
#$output | Sort LocalPath | ft * -AutoSize | Out-String -Width 4096 | Out-File -filepath "C:\MyOutput.TXT" -append -Encoding Unicode
Write-EventLog –LogName Application –Source "Stone Profile Cleanup" –EntryType Information –EventID 1701 -Category 2 -Message ("Profiles older than $MaximumProfileAge days have been cleaned up")
}
I found this piece of code for the computer input but I'm not sure how I can implement it properly.
Get-CimInstance -ComputerName SRV1,SRV2,SRV3 -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq 'UserA' } | Remove-CimInstance
Thanks for the help everyone.
Get-CimInstance -ComputerName SRV1,SRV2,SRV3 -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('')[-1] -eq 'UserA' } | Remove-CimInstance
Do u test it before? Work OK?

Optimize slow powershell script

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

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

PowerShell processing of large users very slow - Is there a better way?

I have been using for Office365 Licence Tracking. Actually it looks like good but but it takes too much time to complete the process. most of time is spent by Get-MsolUser it may be improved calculating them in parallel (while processing user 1 you're already fetching user 2's data and so on...) BTW we have about 3000+ cloud user How can I improve the speed of the script?
$T1 = #()
$O365Users = Get-MsolUser -All
ForEach ($O365User in $O365Users)
{
$ADuser = Get-ADUser -Filter { UserPrincipalName -eq $O365User.UserPrincipalName } -Properties whenCreated, Enabled, lastlogondate
$O365Stats = Get-MailboxStatistics $O365User.DisplayName -ErrorAction SilentlyContinue
$O365Smtp = Get-Recipient $O365User.DisplayName -ErrorAction SilentlyContinue
If ($O365Stats -and $O365Smtp) {
If (($ADUser.Enabled -eq $true) -and ($O365User.isLicensed -eq $true))
{
$T1 += New-Object psobject -Property #{
CollectDate = $(Get-Date);
ADUserUPN = $($ADUser.UserPrincipalName);
O365UserUPN = $($O365User.UserPrincipalName);
ADUserCreated = $($ADUser.whenCreated);
ADUserEnabled = $($ADUser.Enabled);
ADLastLogonDate = $($ADUser.LastLogonDate);
O365Licensed = $($O365User.isLicensed);
O365LastLogonTime = $($O365Stats.LastLogonTime);
O365SMTPAddress = $($O365Smtp.PrimarySMTPAddress)
}
}
}
}
$T1 = $T1 | Sort-Object -Property ADUserCreated
$T1 | Format-Table
$T1 | Export-Csv -Path $OutputFile -NoTypeInformation
Write-Host "Output to $OutputFile"
Using a pipeline, filtering early on, and avoiding appending to an array should already speed things up considerably:
Get-MsolUser -All | Where-Object {
$_.IsLicensed
} | ForEach-Object {
$upn = $_.UserPrincipalName
Get-ADUser -Filter "UserPrincipalName -eq '$upn'" -Properties whenCreated, Enabled, lastlogondate
} | Where-Object {
$_.Enabled
} | ForEach-Object {
$O365Stats = Get-MailboxStatistics $_.DisplayName -ErrorAction SilentlyContinue
$O365Smtp = Get-Recipient $_.DisplayName -ErrorAction SilentlyContinue
if ($O365Stats -and $O365Smtp) {
New-Object -Type PSObject -Property #{
'CollectDate' = Get-Date
'ADUserUPN' = $_.UserPrincipalName
'O365UserUPN' = $_.UserPrincipalName
'ADUserCreated' = $_.whenCreated
'ADUserEnabled' = $_.Enabled
'ADLastLogonDate' = $_.LastLogonDate
'O365Licensed' = $true
'O365LastLogonTime' = $O365Stats.LastLogonTime
'O365SMTPAddress' = $O365Smtp.PrimarySMTPAddress
}
}
} | Sort-Object -Property ADUserCreated | Export-Csv -Path $OutputFile -NoType
Also, why the heck is everybody so infatuated with subexpressions? Use them where you need them. Don't obfuscate your code with them when they're unnecessary.
To give you a set off with parallelism in Powershell.
I would like you to go through the PS Workflows.
We have -parallel in that which will help you in parallel call.
Apart from that, we have one function for Invoke-Parallel
This is the link for it : Invoke-Parallel Function
Note: Examples are mentioned inside the function itself . You can use get-help with that function also once compiled.

Append CSV output coding issue

Hi i created the below script to audit local admin groups on remote hosts. It works fine, but since it only outputs the data after it has went through all the hosts, i am afraid the array will run out of buffer space before it has a chance to export to csv, so i have been trying to have it create and append the output from each host as it goes through the list except i cannot get the headers to display on the first line and append additonal lines below it. Below is the output i get when i try to append. The italicized words should be the headers and the other info should be listed in the next row. what am i doing wrong?
#{Server=pc1; Members=Administrator;DistinguishedName=DC=Domain,DC=com
This is how it should look. It looks this way if i dont append and i let it create the csv after it has finished going through the list of hosts
Server Members DistinguishedName
host1 Administrator;Admin2 DC=DOMAIN,DC=COM
$servers= get-content "C:\Scripts\AD Audits\Local Admin\workstations.txt"
$output = "c:\temp\local admin audit $CurrentDate.csv"
$results = #()
$disconnected = "Did not respond"
foreach($server in $servers)
{
$connected = Test-Connection $server -count 1 -quiet
if ($connected) {
"$server responded" | Out-File -append "c:\temp\LocalAdmin goodhosts $CurrentDate.txt"}
else {
"$server did not respond" | Out-File -append "c:\temp\LocalAdmin badhosts $CurrentDate.txt"}
$group =[ADSI]"WinNT://$server/Administrators"
$members = $group.Members() | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
$results += New-Object PsObject -Property #{
DistinguishedName = (get-adcomputer ($server) -properties * | select distinguishedname).distinguishedname
Server = $server
Members = $members -join ";"
}
$results | Export-Csv $Output -NoTypeInformation
}`
if($connected -eq $True) {
New-Object PSObject -Property #{
DistinguishedName = (Get-ADComputer $_).DistinguishedName
Server = $_
Members = $members -join ";"
}} else {write-host ""}
My suggestion is to use the pipeline rather than a foreach statement, so each object is written to the file as soon as it's processed.
$servers | ForEach-Object{
$connected = Test-Connection $_ -Count 1 -Quiet -ErrorAction SilentlyContinue
$state = if($connected) {"$_ responded"} else {"$_ did not respond"}
$state | Out-File -Append "c:\temp\LocalAdmin goodhosts $CurrentDate.txt"
$group =[ADSI]"WinNT://$_/Administrators,group"
$members = $group.Members() | ForEach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
if($connected)
{
New-Object PSObject -Property #{
DistinguishedName = (Get-ADComputer $_).DistinguishedName
Server = $_
Members = $members -join ";"
}
}
} | Export-Csv $Output -NoTypeInformation