PowerShell Jobs vs Start-Process - powershell

I made a little diagnostic script that I keep in my $profile. In collecting the CPU name I found that the command takes about 4 seconds (Get-WmiObject -Class Win32_Processor).Name. So I thought I'd try PowerShell Jobs and while I think they will be really good for long background jobs, if you just want to quickly grab a small piece of information in the background, the initialisation times are awkward (like 2-3 sec per job) so I thought I'd use Start-Process to dump values in temp files while the rest of my script runs. I think I'm doing this correctly, but if you run this function 3 or 4 times, you'll notice that CPU name is not populated.
• Is using Start-Process like this optimal, or does anyone have a quicker way to just start small jobs in the background in parallel? I know there is a .NET way of doing this (but it seems super-complex from what I've seen)?
• Do you know why my "wait for file to be created and be non-zero before accessing it" is failing so regularly?
function sys {
$System = get-wmiobject -class "Win32_ComputerSystem"
$Mem = [math]::Ceiling($System.TotalPhysicalMemory / 1024 / 1024 / 1024)
$wmi = gwmi -class Win32_OperatingSystem -computer "."
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
$s = "" ; if ($uptime.Days -ne 1) {$s = "s"}
$uptime_string = "$($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min $($uptime.seconds) sec"
$temp_cpu = "$($env:TEMP)\ps_temp_cpu.txt"
$temp_cpu_cores = "$($env:TEMP)\ps_temp_cpu_cores.txt"
$temp_cpu_logical = "$($env:TEMP)\ps_temp_cpu_logical.txt"
rm -force $temp_cpu -EA silent ; rm -force $temp_cpu_cores -EA silent ; rm -force $temp_cpu_logical -EA silent
Start-Process -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoLogo -NoProfile (Get-WmiObject -Class Win32_Processor).Name > $temp_cpu"
Start-Process -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoLogo -NoProfile (Get-WmiObject -Class Win32_Processor).NumberOfCores > $temp_cpu_cores"
Start-Process -NoNewWindow -FilePath "powershell.exe" -ArgumentList "-NoLogo -NoProfile (Get-WmiObject -Class Win32_Processor).NumberOfLogicalProcessors > $temp_cpu_logical"
""
"Hostname: $($System.Name)"
"Domain: $($System.Domain)"
"PrimaryOwner: $($System.PrimaryOwnerName)"
"Make/Model: $($System.Manufacturer) ($($System.Model))" # "ComputerModel: $((Get-WmiObject -Class:Win32_ComputerSystem).Model)"
"SerialNumber: $((Get-WmiObject -Class:Win32_BIOS).SerialNumber)"
"PowerShell: $($PSVersionTable.PSVersion)"
"Windows Version: $($PSVersionTable.BuildVersion)"
"Windows ReleaseId: $((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'ReleaseId').ReleaseId)"
"Display Card: $((Get-WmiObject -Class:Win32_VideoController).Name)"
"Display Driver: $((Get-WmiObject -Class:Win32_VideoController).DriverVersion)"
"Display ModelDesc: $((Get-WmiObject -Class:Win32_VideoController).VideoModeDescription)"
"Last Boot Time: $([Management.ManagementDateTimeConverter]::ToDateTime((Get-WmiObject Win32_OperatingSystem | select 'LastBootUpTime').LastBootUpTime))" # $(wmic OS get LastBootupTime)
"Uptime: $uptime_string"
# ipconfig | sls IPv4
Get-Netipaddress | where AddressFamily -eq IPv4 | select IPAddress,InterfaceIndex,InterfaceAlias | sort InterfaceIndex
# Get-PSDrive | sort -Descending Free | Format-Table
# https://stackoverflow.com/questions/37154375/display-disk-size-and-freespace-in-gb
# https://www.petri.com/checking-system-drive-free-space-with-wmi-and-powershell
# https://www.oxfordsbsguy.com/2017/02/08/powershell-how-to-check-for-drives-with-less-than-10gb-of-free-diskspace/
# Get-Volume | Where-Object {($_.SizeRemaining -lt 10000000000) -and ($_.DriveType -eq “FIXED”) -and ($_.FileSystemLabel -ne “System Reserved”)}
gwmi win32_logicaldisk | Format-Table DeviceId, VolumeName, #{n="Size(GB)";e={[math]::Round($_.Size/1GB,2)}},#{n="Free(GB)";e={[math]::Round($_.FreeSpace/1GB,2)}}
# Note: -EA silent on Get-Item otherwise get an error
while (!(Test-Path $temp_cpu)) { while ((Get-Item $temp_cpu -EA silent).length -eq 0kb) { Start-Sleep -Milliseconds 500 } }
"CPU: $(cat $temp_cpu)"
while (!(Test-Path $temp_cpu_cores)) { while ((Get-Item $temp_cpu_cores -EA silent).length -eq 0kb) { Start-Sleep -Milliseconds 500 } }
"CPU Cores: $(cat $temp_cpu_cores)"
while (!(Test-Path $temp_cpu_logical)) { while ((Get-Item $temp_cpu_logical -EA silent).length -eq 0kb) { Start-Sleep -Milliseconds 500 } }
"CPU Logical: $(cat $temp_cpu_logical)"
rm -force $temp_cpu -EA silent ; rm -force $temp_cpu_cores -EA silent ; rm -force $temp_cpu_logical -EA silent
"Memory: $(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum | Foreach {"{0:N2}" -f ([math]::round(($_.Sum / 1GB),2))}) GB"
""
"Also note the 'Get-ComputerInfo' Cmdlet (has more info but slower to run)"
""
}

To run jobs in background in powershell, there are these 3 ways to go about it
1. Invoke-Command[3] -scriptblock { script } -asJob -computername localhost
2. Start-Job[2] -scriptblock { script }
3. Start-Process[1] powershell {script}
If you truly want to run things in the background with each job being independent of each other, you'll have to think about using the first or second option as neither of them require the output to be written to a file.
Invoke-Command starts a new session with the system and runs the job in a new instance.
Start-Job creates a new job in the background under a new powershell instance, takes a little more time to allocate the resources and start the process. Just like start-process, Start-Job will run the job in a separate powershell.exe instance.
Start-Process requires you to redirect the standard output to a file[1]. You have to rely on the performance of the disk and how fast your reads and writes are. You also have to ensure that no more than one thread is reading/writing to the output of this process.
Recommendation
I found Invoke-Command to be the fastest when running 100 concurrent jobs to get the processor info. This option does require you to provide -ComputerName which then requires you to be an admin to start a winrm Session with localhost. If you dont output the job information while creating the jobs, it does not take away any significant time.
Start-Job and Invoke-Command both took about a second to get the processor info and running 100 concurrent jobs to get the same thing took some overhead.
$x = 0..100 | Invoke-Command -computername localhost -scriptblock { script } -asJob
$x | % { $_ | wait-job | out-null }
$output = $x | % { $_ | Receive-Job}
# You can run measure-object, sort-object, etc as well
[1]Start-Process
RedirectStandardOutput: Specifies a file. This cmdlet sends the output generated by the process to a file that you specify. Enter the path and filename. By default, the output is displayed in the console.
[2]Start-Job
The Start-Job cmdlet starts a PowerShell background job on the local computer. ... A PowerShell background job runs a command without interacting with the current session.
[3]Invoke-Command
The Invoke-Command cmdlet runs commands on a local or remote computer and returns all output from the commands, including errors. ... To run a command in a background job, use the AsJob parameter

Related

Two Invoke-Command at the same time

I'm using this PowerShell script to resume Bitlocker on every active device:
Get-Content "clients.txt" | ForEach-Object {
if (Test-Connection $_ -Count 1 -ErrorAction 0 -Quiet) {
Invoke-Command -ComputerName $_ -ScriptBlock {
Resume-BitLocker -MountPoint "C:"
}
} else {
Write-Host "$_ is OFFLINE" -ForegroundColor Red
}
}
But I also want to trigger a hardware inventory via Invoke-WMIMethod on every active device with this command:
Invoke-WMIMethod -ComputerName $Server -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000001}"
I was able to script the first part but it isn't that well to built in the second command.
you are drifting a bit in the wrong direction.
When using Invoke-Command, it processes the scriptblock, against 32 computers simultaneously (in parallel)!
If you are processing computers with foreach, it would handle them sequentially, which would be much slower.
Same is valid when using *WMI cmdlets. Always try to replace them with the corresponding CIM cmdlets, as the same logic applies - computers are being processed in parallel.
Consider something like:
$ComputerList = Get-Content -Path Clients.txt
Invoke-Command -ComputerName $ComputerList -ErrorAction SilentlyContinue -ScriptBlock {
Resume-BitLocker -MountPoint "C:"
#Add second command
#add third command and so on
}
I am not sure, what would be the alternative command to Invoke-WMIMethod, when executing locally. Maybe Set-WMIInstance, but I am only speculating!
Then if you would like to add second command for execution, just add it into the scriptblock of Invoke-Command.

Unable to re-write the file in powershell using Out-File

I am trying to write a powershell script to write the output of a command to a file. It runs perfectly fine for the first time. When i re-run the script it comes with an error
Out-File : The process cannot access the file '//Filepath' because it is
being used by another process.
+ (Invoke-Command -Session $s -ScriptBlock $command )| Out-File
$file -For ...
Code block :
$command = {
$x = (Get-Date).AddMinutes(-5).ToShortTimeString() ;
$y = "{0:HH:mm}" -f [datetime] $x ;
cd $path ;
dumplog ctisvr /bt $y /m "CSTAUniversalFailureConfEvent"
}
#$s = New-PSSession -ComputerName
(Invoke-Command -Session $s -ScriptBlock $command )| Out-File $file -Force
My suggestion would be that this is not PoSH cause issue but the dumplog command you are using. There are programs with will not lock files and those that will. I've never used dumplog, I can only speculate here.
So, it would be best to check for the process state of the exe and file before doing anything with it afterwards.
Like simply looking for a dumplog process after the first serialization is done, the kill any dumplog process before trying again.
If this is on a file share you can use...
Get-SmbOpenFile
Or
openfiles
If it is local you can do something like...
What file is open by a given program
Get-CimInstance Win32_Process -Filter "name = 'dumplog.exe'" | Format-List -Property *
Get the owner of the process
$ProcessId = Get-CimInstance Win32_Process -Filter "name = 'dumplog.exe'"
Invoke-CimMethod -InputObject $ProcessId -MethodName GetOwner
What process has a file locked or filter the results based on the file name:
($ProcessId = Get-CimInstance Win32_Process | where commandline -match 'CSTAUniversalFailureConfEvent')
Kill the process
Invoke-CimMethod -InputObject $ProcessId -MethodName Terminate

Powershell Background Job - How do I get output from Export-Counter/Get-Counter with 'ComputerName' parameter?

I am trying to collect performance data from a powershell background job while applying 'load' to the system in the foreground.
If I run my Get-Counter/Export-Counter script without a -ComputerName parameter, as a background job, it creates an output file with performance data from the local computer, as expected.
# Background job, No ComputerName
$scriptBlockStr = "Get-Counter -Counter ""\Memory\Available MBytes"" -SampleInterval 2 -MaxSamples 3 | Export-Counter -Force -FileFormat CSV -Path $PSScriptRoot\MinPerfTest.csv"
$sb = [scriptblock]::Create($ScriptBlockStr)
$j = Start-Job -Name "PerfMon01" -ScriptBlock $sb
Start-Sleep -Seconds 10
Stop-Job $j.Id
Write-Host "See $PSScriptRoot\MinPerfTest.csv."
If I include the -ComputerName parameter, and run the script block in the foreground, it creates the output file with performance data from the specified computer.
# Foreground job, With ComputerName
$scriptBlockStr = "Get-Counter -Counter ""\Memory\Available MBytes"" -ComputerName ""\\CPQDEV.fpx.com"" -SampleInterval 2 -MaxSamples 3 | Export-Counter -Force -FileFormat CSV -Path $PSScriptRoot\MinPerfTest.csv"
$sb = [scriptblock]::Create($ScriptBlockStr)
& $sb
Write-Host "See $PSScriptRoot\MinPerfTest.csv. (Wait! It can take a while.)"
But if I run the script with the -ComputerName parameter, as a background job, the Export-Counter cmdlet never produces any output.
# Background job, With ComputerName
$scriptBlockStr = "Get-Counter -Counter ""\Memory\Available MBytes"" -ComputerName ""\\CPQDEV.fpx.com"" -SampleInterval 2 -MaxSamples 3 | Export-Counter -Force -FileFormat CSV -Path $PSScriptRoot\MinPerfTest.csv"
$sb = [scriptblock]::Create($ScriptBlockStr)
$j = Start-Job -Name "PerfMon01" -ScriptBlock $sb
Start-Sleep -Seconds 10
Stop-Job $j.Id
Write-Host "See $PSScriptRoot\MinPerfTest.csv. (Wait! It could take a while, if it works at all.)"
Can you tell me what I need to do to get performance data from named computers?
Thank you!
Reference
Your issue is with remote jobs. Here's an easier suggestion (from msdn):
$SB = [ScriptBlock]::Create('Get-Counter -Counter "Memory\Available MBytes" -SampleInterval 2 -MaxSamples 3')
Invoke-Command -ComputerName \\CPQDEV.fpx.com -ScriptBlock $SB -AsJob |
Receive-Job -Wait |
Export-Counter -Force -FileFormat CSV -Path "$PSScriptRoot\MinPerfTest.csv"
Write-Host "See $PSScriptRoot\MinPerfTest.csv. (may not work)"

Powershell Wait-process until installation complete [duplicate]

As part of a backup operation, I am running the 7zip command to compress a folder into a single .7z file. No problems there as I am using the InVoke-WMIMethod.
Example:
$zip = "cmd /c $irFolder\7za.exe a $somedirectory.7z $somedirectory"
"InVoke-WmiMethod -class Win32_process -name Create -ArgumentList $zip -ComputerName $remotehost"
My problem comes in as my script continues, the 7za.exe process hasn't completed. I am then attempting to copy the item off of the remote system and it is either incomplete or fails.
Can someone point me in the direction to figure out how to identify if the 7za.exe process is still running, wait until it is dead, then proceed with the rest of my script?
I can grasp pulling the process from the remote system via...
get-wmiobject -class Win32_Process -ComputerName $remotehost | Where-Object $_.ProcessName -eq "7za.exe"}
Not sure how to turn that into usable info for my issue.
Answer UPDATE: (thx to nudge by #dugas)
This will do it with some feedback for those that need it...
do {(Write-Host "Waiting..."),(Start-Sleep -Seconds 5)}
until ((Get-WMIobject -Class Win32_process -Filter "Name='7za.exe'" -ComputerName $target | where {$_.Name -eq "7za.exe"}).ProcessID -eq $null)
You can invoke the Wait-Process cmdlet on the remote computer with the Invoke-Command cmdlet. Example:
$process = Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList notepad -ComputerName RemoteComputer
Invoke-Command -ComputerName RemoteComputer -ScriptBlock { param($processId) Wait-Process -ProcessId $processId } -ArgumentList $process.ProcessId
Since you mentioned using Invoke-Command is not an option, another option is polling.
Example:
$process = Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList notepad -ComputerName hgodasvccr01
$processId = $process.ProcessId
$runningCheck = { Get-WmiObject -Class Win32_Process -Filter "ProcessId='$processId'" -ComputerName hgodasvccr01 -ErrorAction SilentlyContinue | ? { ($_.ProcessName -eq 'notepad.exe') } }
while ($null -ne (& $runningCheck))
{
Start-Sleep -m 250
}
Write-Host "Process: $processId is not longer running"
You should be able to do it with a do... while loop that just sleeps until the process is finished.
do {
"waiting"
start-sleep 10
} while (gwmi -class win32_process -ComputerName $remotehost | Where ProcessName -eq "7za.exe")

Powershell - Check on Remote Process, if done continue

As part of a backup operation, I am running the 7zip command to compress a folder into a single .7z file. No problems there as I am using the InVoke-WMIMethod.
Example:
$zip = "cmd /c $irFolder\7za.exe a $somedirectory.7z $somedirectory"
"InVoke-WmiMethod -class Win32_process -name Create -ArgumentList $zip -ComputerName $remotehost"
My problem comes in as my script continues, the 7za.exe process hasn't completed. I am then attempting to copy the item off of the remote system and it is either incomplete or fails.
Can someone point me in the direction to figure out how to identify if the 7za.exe process is still running, wait until it is dead, then proceed with the rest of my script?
I can grasp pulling the process from the remote system via...
get-wmiobject -class Win32_Process -ComputerName $remotehost | Where-Object $_.ProcessName -eq "7za.exe"}
Not sure how to turn that into usable info for my issue.
Answer UPDATE: (thx to nudge by #dugas)
This will do it with some feedback for those that need it...
do {(Write-Host "Waiting..."),(Start-Sleep -Seconds 5)}
until ((Get-WMIobject -Class Win32_process -Filter "Name='7za.exe'" -ComputerName $target | where {$_.Name -eq "7za.exe"}).ProcessID -eq $null)
You can invoke the Wait-Process cmdlet on the remote computer with the Invoke-Command cmdlet. Example:
$process = Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList notepad -ComputerName RemoteComputer
Invoke-Command -ComputerName RemoteComputer -ScriptBlock { param($processId) Wait-Process -ProcessId $processId } -ArgumentList $process.ProcessId
Since you mentioned using Invoke-Command is not an option, another option is polling.
Example:
$process = Invoke-WmiMethod -Class Win32_Process -Name create -ArgumentList notepad -ComputerName hgodasvccr01
$processId = $process.ProcessId
$runningCheck = { Get-WmiObject -Class Win32_Process -Filter "ProcessId='$processId'" -ComputerName hgodasvccr01 -ErrorAction SilentlyContinue | ? { ($_.ProcessName -eq 'notepad.exe') } }
while ($null -ne (& $runningCheck))
{
Start-Sleep -m 250
}
Write-Host "Process: $processId is not longer running"
You should be able to do it with a do... while loop that just sleeps until the process is finished.
do {
"waiting"
start-sleep 10
} while (gwmi -class win32_process -ComputerName $remotehost | Where ProcessName -eq "7za.exe")