Match two get-process variables isnt working - powershell

with my code I want to get the ProcessID of a programm called VNCviewer, then I open up a new session in this programm at this point I've got the ProcessID of the Overview console and a opened Session without knowing the ProcessID (both tasks are named the same). Now I want to find the ProcessID of the new Session to get this I did the loop to match both processID and kill the one which isnt the one saved in my first variable but its not working I'm getting the error that the variable "PID" is read only. Someone has a solution for me?
$NVCOverview = Get-Process vncviewer
$wshell = New-Object -ComObject wscript.shell;
start-sleep -Milliseconds 1000
(New-Object -ComObject WScript.Shell).AppActivate((get-process vncviewer).MainWindowTitle)
Start-Sleep -Milliseconds 100
$wshell.SendKeys("{TAB}")
Start-Sleep -Milliseconds 100
$wshell.SendKeys("{TAB}")
Start-Sleep -Milliseconds 100
$wshell.SendKeys("{ENTER}")
Start-Sleep -Milliseconds 5000
$newVncProcesses = get-process vncviewer | where {$_.Id -NotLike $NVCOverview[0]}
foreach ($pid in $newVncProcesses)
{
Stop-Process $pid
}
I expect that the loop will catch the ID of the neweset Session and kills it.

$PID is a reserved automatic variable in Powershell that holds the Process ID of the current Powershell session, and therefore read-only like the message suggests.
Change $pid to $id or something like that and it should work like expected.
See more about the many automatic variables in the documentation, some can be quite useful.
EDIT: Your filter is a bit off too, you're comparing only the Id property of the current object to the whole object of the original variable, and using an index of zero that doesn't make sense for a variable containing a single process. This:
$newVncProcesses = get-process vncviewer | where {$_.Id -NotLike $NVCOverview[0]}
should probably look more like this:
$newVncProcesses = get-process vncviewer | where {$_.Id -ne $NVCOverview.Id}

Related

Test-Path timeout for PowerShell

I'm trying to routinely check the presence of particular strings in text files on hundreds of computers on our domain.
foreach ($computer in $computers) {
$hostname = $computer.DNSHostName
if (Test-Connection $hostname -Count 2 -Quiet) {
$FilePath = "\\" + $hostname + "c$\SomeDirectory\SomeFile.txt"
if (Test-Path -Path $FilePath) {
# Check for string
}
}
}
For the most part, the pattern of Test-Connection and then Test-Path is effective and fast. There are certain computers, however, that ping successfully but Test-Path takes around 60 seconds to resolve to FALSE. I'm not sure why, but it may be a domain trust issue.
For situations like this, I would like to have a timeout for Test-Path that defaults to FALSE if it takes more than 2 seconds.
Unfortunately the solution in a related thread (How can I wrap this Powershell cmdlet into a timeout function?) does not apply to my situation. The proposed do-while loop gets hung up in the code block.
I've been experimenting with Jobs but it appears even this won't force quit the Test-Path command:
Start-Job -ScriptBlock {param($Path) Test-Path $Path} -ArgumentList $Path | Wait-Job -Timeout 2 | Remove-Job -Force
The job continues to hang in the background. Is this the cleanest way I can achieve my requirements above? Is there a better way to timeout Test-Path so the script doesn't hang besides spawning asynchronous activities? Many thanks.
Wrap your code in a [powershell] object and call BeginInvoke() to execute it asynchronously, then use the associated WaitHandle to wait for it to complete only for a set amount of time.
$sleepDuration = Get-Random 2,3
$ps = [powershell]::Create().AddScript("Start-Sleep -Seconds $sleepDuration; 'Done!'")
# execute it asynchronously
$handle = $ps.BeginInvoke()
# Wait 2500 milliseconds for it to finish
if(-not $handle.AsyncWaitHandle.WaitOne(2500)){
throw "timed out"
return
}
# WaitOne() returned $true, let's fetch the result
$result = $ps.EndInvoke($handle)
return $result
In the example above, we randomly sleep for either 2 or 3 seconds, but set a 2 and a half second timeout - try running it a couple of times to see the effect :)

Start-Sleep pauses entire function

I have a PowerShell script with a function to pull LAPS information when someone enters a computer name. What I'd like is to clear the output after 60 seconds so someone doesn't accidentally leave a password on their screen.
Seems as though no matter where the sleep is in the function (or even after) the script is paused for the 60 seconds, then displays the information and it never clears.
The part of the script that waits and clears:
Start-Sleep -s 60
$WPFOutputbox.Clear()
(look for my #TRIED HERE comments below)
function GETLAPS {
Param ($computername = $WPFComputer.Text)
try {
$LAPSComputer = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty computername
$LAPSDistinguished = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty distinguishedname
$LAPSPassword = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty Password
$LAPSExpire = Get-AdmPwdPassword -ComputerName $computername |
Select-Object -ExpandProperty Expirationtimestamp
} catch {
$ErrorMessage = $_.Exception.Message
}
if ($ErrorMessage -eq $null) {
$WPFOutputBox.Foreground = "Blue"
$WPFOutputBox.Text = "Local Admin Information for: $LAPSComputer
Current: $LAPSPassword
Exipiration: $LAPSExpire
SCREEN WILL CLEAR AFTER 60 SECONDS"
#TRIED HERE
} else {
$WPFOutputBox.Foreground = "Red"
$WPFOutputBox.Text = $ErrorMessage
}
#TRIED HERE
}
Instead the script will wait 60 seconds to show information, and never clear the screen.
Unless you create a separate thread, I would strongly advise against using the Start-Sleep cmdlet in Windows Forms or Windows Presentation Foundation as it will obviously stall the User Interface.
Instead, I would recommend to use a (Windows.Forms.Timer) event.
For this, I wrote a small function to delay a command:
Delay-Command
Function Delay-Command([ScriptBlock]$Command, [Int]$Interval = 100, [String]$Id = "$Command") {
If ($Timers -isnot "HashTable") {$Global:Timers = #{}}
$Timer = $Timers[$Id]
If (!$Timer) {
$Timer = New-Object Windows.Forms.Timer
$Timer.Add_Tick({$This.Stop()})
$Timer.Add_Tick($Command)
$Global:Timers[$Id] = $Timer
}
$Timer.Stop()
$Timer.Interval = $Interval
If ($Interval -gt 0) {$Timer.Start()}
}; Set-Alias Delay Delay-Command
Example
Delay {$WpFOutputBox.Text = ''} 60000
Parameters
-Command
The command to be delayed.
-Interval
The time to wait before executing the command.
If -Interval is set to 0 or less, the command (with the related Id) will be reset.
The default is 100 (a minor time lapse to give other events the possibility to kick off).
-Id
The Id of the delayed command. Multiple commands which different Id's can be executed simultaneously. If the same Id is used for a command that is not yet executed, the interval timer will be reset (or canceled when set to 0).
The default Id in the command string.
Call the function, sleep 60 seconds, then clear it. Do not put your sleep inside the function, put it where you call the function. Something like:
$WPFGetLAPSButton.add_click({
GETLAPS
# If no error, wait 60 seconds and clear the text box
If($WPFOutputBox.Foreground -eq "Blue"){
Start-Sleep 60
$WPFOutputBox.Text = ''
}
})
Pretty sure that'll do what you're looking for.

Wait for all child processes to finish

Currently I am trying to run a script multiple times and let the parent wait for all the child processes to finish.
The creation of the children is as follows:
# Spawn balance load processes
$command = "-i $ScriptPath\balanceLoaders\%BALANCE_DIR% -o $outputDirectory -c %BALANCE_DIR%"
#$command = "$ScriptPath\convertFiles.ps1 -i $ScriptPath\balanceLoaders\%BALANCE_DIR% -o $outputDirectory -c %BALANCE_DIR%"
for ([int]$i = 0; $i -lt $b; $i++) {
#$proc = Start-Process powershell.exe -WindowStyle Hidden -Argument $command.Replace('%BALANCE_DIR%', $i) -PassThru
$proc = Start-Process $ScriptPath\convertfiles.exe -WindowStyle Hidden -Verb Runas -ArgumentList $command.Replace('%BALANCE_DIR%', $i) -PassThru
}
The children are spawned dynamically. Currently I can only track the last spawned child with $proc. However, I want to track them all.
Currently using $proc.WaitForExit() to wait for the child.
Collect all processes in an array and run an idle loop until all of them have exited.
$proc = for ($i = 0; $i -lt $b; $i++) {
Start-Process ... -PassThru
}
while (($proc | Select-Object -Expand HasExited) -contains $false) {
Start-Sleep -Milliseconds 100
}
If you have PowerShell v3 or newer you can simplify the loop to something like this:
while ($proc.HasExited -contains $false) {
Start-Sleep -Milliseconds 100
}
because PowerShell v3 introduced a new feature called member enumeration that allows accessing properties or methods of array elements via the array object.
Ansgar Wiecher's helpful answer contains good pointers and a working solution, but if you simply want to wait for all spawned processes to terminate, use of Wait-Process is simpler.
That said, if you want to check exit codes as they become available, before all processes have exited, a loop that sleeps periodically (Start-Sleep), as in Ansgar's answer, is necessary.
The following solution demonstrates the synchronously-wait-for-all technique with Wait-Process with a simplified example that creates 3 instances of Notepad and waits for all of them to terminate:
# Create all processes and store objects representing them in array $procs.
$b = 3 # number of processes to create
$procs = foreach ($i in 0..($b-1)) { Start-Process -PassThru Notepad }
# Wait for all processes to exit.
# (Close the Notepad windows manually or, if no Notepad windows were previously
# open, use `Stop-Process -Name Notepad` from another session to forcefully close them.)
# Use Wait-Process -Timeout <secs> to limit the max. period of time to wait.
$procs | Wait-Process
# Get the exit codes.
$exitCodes = $procs.ExitCode # PSv3+; v2: $procs | Select-Object -ExpandProperty ExitCode
Or, as a single pipeline:
# Use $_ inside { ... } to refer to the current iteration value.
# Use $procs.ExitCode to get the exit codes.
0..($b-1) | ForEach-Object -ov procs { Start-Process -PassThru Notepad } | Wait-Process
Note the use of -ov - short for common parameter -OutVariable, which collects the process objects output by the ForEach-Object cmdlet's script block invocations.

Powershell Wait for service to be stopped or started

I have searched both this forum and through google and can't find what I need.
I have a quite large script and I'm looking for some code that will check if the service is started or stopped before proceeding to the next step.
The function it self need to loop untill it's either stopped or started (Going to have a function for Stopped and one for Started).
In total 4 services which almost have the same name, so Service Bus * can be used as a wildcard.
I couldn't get the 'count' strategy, that Micky posted, to work, so here is how i solved it:
I created a function, that takes a searchString (this could be "Service Bus *") and the status that i expect the services should reach.
function WaitUntilServices($searchString, $status)
{
# Get all services where DisplayName matches $searchString and loop through each of them.
foreach($service in (Get-Service -DisplayName $searchString))
{
# Wait for the service to reach the $status or a maximum of 30 seconds
$service.WaitForStatus($status, '00:00:30')
}
}
The function can now be called with
WaitUntilServices "Service Bus *" "Stopped"
or
WaitUntilServices "Service Bus *" "Running"
If the timeout period is reached, a not so graceful exception is thrown:
Exception calling "WaitForStatus" with "2" argument(s): "Time out has expired and the operation has not been completed."
In addition to the answer of mgarde this one liner might be useful if you just want to wait for a single service (also inspired by a post from Shay Levy):
(Get-Service SomeInterestingService).WaitForStatus('Running')
The following will loop and verify the status of the given services until the number of services with the "Running" state is equal to zero (hence they are stopped), so you can use this if you are waiting for services to Stop.
I've added a $MaxRepeat variable, which will prevent this from running for ever. It will run 20 times max as defined.
$services = "Service Bus *"
$maxRepeat = 20
$status = "Running" # change to Stopped if you want to wait for services to start
do
{
$count = (Get-Service $services | ? {$_.status -eq $status}).count
$maxRepeat--
sleep -Milliseconds 600
} until ($count -eq 0 -or $maxRepeat -eq 0)
I had to tweak this a bit with multiple counters because this service purposely starts and stops slowly. The original script got me on the right track. I had to wait for the service to be in a completely stopped status before I could move on because I'm actually restarting that same service.
You could probably remove the "sleep," but I don't mind leaving it in.
You could probably remove everything and just use the $stopped variable. :)
# change to Stopped if you want to wait for services to start
$running = "Running"
$stopPending = "StopPending"
$stopped = "Stopped"
do
{
$count1 = (Get-Service $service | ? {$_.status -eq $running}).count
sleep -Milliseconds 600
$count2 = (Get-Service $service | ? {$_.status -eq $stopPending}).count
sleep -Milliseconds 600
$count3 = (Get-Service $service | ? {$_.status -eq $stopped}).count
sleep -Milliseconds 600
} until ($count1 -eq 0 -and $count2 -eq 0 -and $count3 -eq 1)
In my Azure build/deployment pipelines I use it like this to start and stop services (after already having sent a 'Stop' command asynchronously before) and which works for all transitional states like Starting, Stopping, Pausing and Resuming (which are called StartPending, StopPending, PausePending and ContinuePending in the status enumeration ServiceControllerStatus).
# Wait for services to be stopped or stop them
$ServicesToStop | ForEach-Object {
$MyService = Get-Service -Name $_ -ComputerName $Server;
while ($MyService.Status.ToString().EndsWith('Pending')) {
Start-Sleep -Seconds 5;
$MyService.Refresh();
};
$MyService | Stop-Service -WarningAction:SilentlyContinue;
$MyService.Dispose();
};
This needs a traditional powershell to function on a remote server, the cmdlet of pwsh.exe does not include parameter -ComputerName.
In my opinion no counters are needed as only transitional states cause the cmdlet to fail and they change anyway to one of the supported states in the near future (maximum 125 seconds for a Stop command).
To add more details to my response to #Christoph
here is a script i recently created to stop services and ensure the processes are also stopped. in our case the processes were predictable. it may be necessary to do more work to get the service/processid mapping if you have multiple services running off the same executeable.
$MaxWait = 180 #seconds
$ServiceNames = "MyServiceName*"
$ProcName = 'MyServiceProcName' #for the services
$sw = [System.Diagnostics.Stopwatch]::StartNew() # to keep track of
$WaitTS = (New-TimeSpan -Seconds $MaxServiceWait) #could also use a smaller interval if you want more progress updates
$InitialServiceState = get-service $ServiceNames | select Name,Status,StartType
write-Host "$ENV:COMPUTERNAME Stopping $ServiceNames"
$sw.Restart()
$Services = #()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending
#make sure the processes are actually stopped!
while (Get-Process | where Name -Match $ProcName)
{
#if there were services still running
if ($Services) {
Write-Host "$ENV:COMPUTERNAME ...waiting up to $MaxServiceWait sec for $($Services.Name)"
#wait for the service to stop
$Services.WaitForStatus("Stopped",$WaitTS)
}
#if we've hit our maximum wait time
if ($sw.Elapsed.TotalSeconds -gt $MaxServiceWait) {
Write-Host "$ENV:COMPUTERNAME Waited long enough, killing processes!"
Get-Process | where name -Match $ProcName | Stop-Process -Force
}
Start-Sleep -Seconds 1
#get current service state and try and stop any that may still be running
#its possible that another process tried to start a service while we were waiting
$Services = #()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending
}

How to run a command against multiple servers simultaneously in Powershell

I am looking for a way to restart three services on multiple servers simultaneously. I know how to restart services against a list of servers by using a loop but as I have many servers it would take a long time to wait for each service on each server to restart in a sequential order. Is there a way to send restart service command to all servers at once instead of waiting for each server?
You could try to work with jobs. Jobs are run in the background and you have to retrieve them with Get-Job to see their status. Please read the information to Powershell jobs on these two sites:
http://msdn.microsoft.com/en-us/library/dd878288%28v=vs.85%29.aspx
http://technet.microsoft.com/de-DE/library/hh847783.aspx
Your code would look something like this:
$servernames | ForEach-Object {Start-Job -Name "Job-$_" -Scriptblock {"Enter your code here -Computername $_"}}
This will create a background job for each servername. As already mentioned you can see the status using the cmdlet Get-Job. To get the result use the cmdlet Receive-Job.
you can use the invoke-command cmdlet
invoke-command -computername computer1,computer2,computer3 {restart-service servicename}
I use and improove a multi-thread Function, you can use it like :
$Script = {
param($Computername)
restart-service servicename -Computername $Computername
}
#('Srv1','Srv2') | Run-Parallel -ScriptBlock $Script
include this code in your script
function Run-Parallel {
<#
.Synopsis
This is a quick and open-ended script multi-threader searcher
http://www.get-blog.com/?p=189#comment-28834
Improove by Alban LOPEZ 2016
.Description
This script will allow any general, external script to be multithreaded by providing a single
argument to that script and opening it in a seperate thread. It works as a filter in the
pipeline, or as a standalone script. It will read the argument either from the pipeline
or from a filename provided. It will send the results of the child script down the pipeline,
so it is best to use a script that returns some sort of object.
.PARAMETER ScriptBlock
This is where you provide the PowerShell ScriptBlock that you want to multithread.
.PARAMETER ItemObj
The ItemObj represents the arguments that are provided to the child script. This is an open ended
argument and can take a single object from the pipeline, an array, a collection, or a file name. The
multithreading script does it's best to find out which you have provided and handle it as such.
If you would like to provide a file, then the file is read with one object on each line and will
be provided as is to the script you are running as a string. If this is not desired, then use an array.
.PARAMETER InputParam
This allows you to specify the parameter for which your input objects are to be evaluated. As an example,
if you were to provide a computer name to the Get-Process cmdlet as just an argument, it would attempt to
find all processes where the name was the provided computername and fail. You need to specify that the
parameter that you are providing is the "ComputerName".
.PARAMETER AddParam
This allows you to specify additional parameters to the running command. For instance, if you are trying
to find the status of the "BITS" service on all servers in your list, you will need to specify the "Name"
parameter. This command takes a hash pair formatted as follows:
#{"key" = "Value"}
#{"key1" = "Value"; "key2" = 321; "key3" = 1..9}
.PARAMETER AddSwitch
This allows you to add additional switches to the command you are running. For instance, you may want
to include "RequiredServices" to the "Get-Service" cmdlet. This parameter will take a single string, or
an aray of strings as follows:
"RequiredServices"
#("RequiredServices", "DependentServices")
.PARAMETER MaxThreads
This is the maximum number of threads to run at any given time. If ressources are too congested try lowering
this number. The default value is 20.
.PARAMETER SleepTimer_ms
This is the time between cycles of the child process detection cycle. The default value is 200ms. If CPU
utilization is high then you can consider increasing this delay. If the child script takes a long time to
run, then you might increase this value to around 1000 (or 1 second in the detection cycle).
.PARAMETER TimeOutGlobal
this is the TimeOut in second for listen the last thread, after this timeOut All thread are closed, only each other are returned
.PARAMETER TimeOutThread
this is the TimeOut in second for each thread, the thread are aborted at this time
.PARAMETER PSModules
List of PSModule name to include for use in ScriptBlock
.PARAMETER PSSapins
List of PSSapin name to include for use in ScriptBlock
.EXAMPLE
1..20 | Run-Parallel -ScriptBlock {param($i) Start-Sleep $i; "> $i sec <"} -TimeOutGlobal 15 -TimeOutThread 5
.EXAMPLE
Both of these will execute the scriptBlock and provide each of the server names in AllServers.txt
while providing the results to GridView. The results will be the output of the child script.
gc AllServers.txt | Run-Parallel $ScriptBlock_GetTSUsers -MaxThreads $findOut_AD.ActiveDirectory.Servers.count -PSModules 'PSTerminalServices' | out-gridview
#>
Param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
$ItemObj,
[ScriptBlock]$ScriptBlock = $null,
$InputParam = $Null,
[HashTable] $AddParam = #{},
[Array] $AddSwitch = #(),
$MaxThreads = 20,
$SleepTimer_ms = 100,
$TimeOutGlobal = 300,
$TimeOutThread = 100,
[string[]]$PSSapins = $null,
[string[]]$PSModules = $null,
$Modedebug = $true
)
Begin{
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
ForEach ($Snapin in $PSSapins){
[void]$ISS.ImportPSSnapIn($Snapin, [ref]$null)
}
ForEach ($Module in $PSModules){
[void]$ISS.ImportPSModule($Module)
}
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.CleanupInterval=1000
$RunspacePool.Open()
$Jobs = #()
}
Process{
#ForEach ($Object in $ItemObj){
if ($ItemObj){
Write-Host $ItemObj -ForegroundColor Yellow
$PowershellThread = [powershell]::Create().AddScript($ScriptBlock)
If ($InputParam -ne $Null){
$PowershellThread.AddParameter($InputParam, $ItemObj.ToString()) | out-null
}Else{
$PowershellThread.AddArgument($ItemObj.ToString()) | out-null
}
ForEach($Key in $AddParam.Keys){
$PowershellThread.AddParameter($Key, $AddParam.$key) | out-null
}
ForEach($Switch in $AddSwitch){
$PowershellThread.AddParameter($Switch) | out-null
}
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = [pscustomobject][ordered]#{
Handle = $Handle
Thread = $PowershellThread
object = $ItemObj.ToString()
Started = Get-Date
}
$Jobs += $Job
}
#}
}
End{
$GlobalStartTime = Get-Date
$continue = $true
While (#($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0 -and $continue) {
ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
$out = $Job.Thread.EndInvoke($Job.Handle)
$out # return vers la sortie srandard
#Write-Host $out -ForegroundColor green
$Job.Thread.Dispose() | Out-Null
$Job.Thread = $Null
$Job.Handle = $Null
}
foreach ($InProgress in $($Jobs | Where-Object {$_.Handle})) {
if ($TimeOutGlobal -and (($(Get-Date) - $GlobalStartTime).totalseconds -gt $TimeOutGlobal)){
$Continue = $false
#Write-Host $InProgress -ForegroundColor magenta
}
if (!$Continue -or ($TimeOutThread -and (($(Get-Date) - $InProgress.Started).totalseconds -gt $TimeOutThread))) {
$InProgress.thread.Stop() | Out-Null
$InProgress.thread.Dispose() | Out-Null
$InProgress.Thread = $Null
$InProgress.Handle = $Null
#Write-Host $InProgress -ForegroundColor red
}
}
Start-Sleep -Milliseconds $SleepTimer_ms
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
}