Better way of writing a script to avoid boolean error - powershell

I have written a script and as part of the script I am checking if a job is running and if it is, forcing it to stop:
$copyjob = Get-Job -Name DBACopy_QFR1-DBA20_2_QFR3-DBS21_S_Drv_DBA -ErrorAction SilentlyContinue
if ($copyjob)
{
remove-job $copyjob -force # This job my be causing problems with the backups so we kill it to make sure.
}
However, I think what I have written is the cause of this error when the script runs:
Cannot convert value "System.Management.Automation.PSRemotingJob" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0
Is there a better way of doing it that doesn't throw up that error, or am I completely incorrect in thinking that this is what is spawning the error.

Instead of
if ($copyjob)
{
}
try using
if ($copyjob -ne $null)
{
}

If that is all your code does, you can simplify that:
Remove-Job -Name DBACopy_QFR1-DBA20_2_QFR3-DBS21_S_Drv_DBA -ErrorAction SilentlyContinue
Less code, less chances to fail. You can also pipeline:
Get-Job -Name "SomeName" -ErrorAction SilentlyContinue | remove-job -force

Related

In Windows Powershell, how can I wait for an event to be true before proceeding?

I'm writing a Windows Powershell script that stops a service, then I want to print the service's status when it's finally stopped. I've tried the following, but it just hangs
$service = Get-Service | Where-Object {$_.name -like "*MyService*"}
Stop-Service -Name $service.name
$wait=true
while ($wait) {
if($service.Status -eq "Running") {
Start-Sleep -Seconds 1
}
else {
$wait=$false
}
}
I know I can probably write a for{} loop instead that counts 0-9, and breaks when my condition is met, but is there a better way?
tanstaafl's helpful answer addresses your immediate problem:
The .Status property value of a [System.ServiceProcess.ServiceController] instance (as returned by Get-Service) is a static value that only reflects the status of the service at the time of the Get-Service call.[1]
To update the value to reflect the then-current status, call the .Refresh() method.
However, there is no need to explicitly wait for a service to stop, because Stop-Service is synchronous, i.e.:
It waits for the service to finish stopping before returning, unless you explicitly pass -NoWait.
If that doesn't happen within a fixed, 2-second timeout:[2]
A warning is issued if the service last reported that stopping is pending - potentially, stopping will eventually finish.
Otherwise, a non-terminating error occurs - this suggests that the service is stuck.
Thus, you can simplify your code as follows:
# Report a script-terminating error if stopping doesn't finish
# within the timeout period.
Stop-Service -Name *MyService* -ErrorAction Stop -WarningAction Stop
More work is needed if you want to implement a retry mechanism.
[1] There is one exception, although the behavior is undocumented and should be considered an implementation detail: If you pipe a preexisting ServiceController instance to Stop-Service / Start-Service, these cmdlets refresh the instance for you; e.g., after executing ($service = Get-Service Bits) | Stop-Service, $service.Status is current (reflects Stopped).
[2] As of PowerShell Core 7.3.0-preview.2 - see the source code.
You need to re-check your service within the loop.
$service = Get-Service | Where-Object {$_.name -like "*MyService*"}
Stop-Service -Name $service.name
$wait=true
while ($wait) {
if($service.Status -eq "Running") {
Start-Sleep -Seconds 1
# ADD THIS BELOW. Need to re-check service in loop.
$service = Get-Service | Where-Object {$_.name -like "*MyService*"}
}
else {
$wait=$false
}
}

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 :)

How do I clear $error and $LASTEXITCODE set by an external cmdlet or executable?

I have a custom module wrapping an external command (csrun.exe), and parses the output so I can use it in PowerShell.
Everything just about works except if the external command writes to stderror, and clearing the error in my cmdlet doesn't seem to fully work. It will clear (i.e. $error.count is 0 and $lasterrorcode is 0, but once I return to the script that is calling my cmdlet, $error and $lasterrorcode are no longer clear and the error in $error references the underlying exception for the external command
System.Management.Automation.RemoteException: The compute emulator is not running.
I've attempted, try-catches, clearing the mentioned variables. Regardless, the calling script retains a reference to the error.
CustomModule.psm1
$__azureEmulatorPath = "C:\Program Files\Microsoft SDKs\Azure\Emulator\"SDKs\Azure\Emulator\"
$__azureEmulator = __azureEmulatorPath + "csrun.exe"
function Get-EmulatorStatus() {
[OutputType([ComputeEmulatorStatus])]
[cmdletbinding()]
param()
$output = (& $__azureEmulator /status | Out-String)
if ($error.Count -gt 0 -or $LASTEXITCODE -ne 0) {
Write-Host ($Error | Format-List -Force | Out-String)
Write-Host Clearing Error and Continuing
$error.Clear()
$LASTEXITCODE = 0
}
#error from command cleared here
return $output
}
export-modulemember -function *
Test.ps1
import-module "CustomModule.psm1" # definew cmdlet Get-EmulatorStatus
$status = Get-EmulatorStatus
# even though error cleared in cmdlet, still here
Write-Host Write-Host Error $LASTEXITCODE, $Error.Count
Write-Host ($Error | Format-List -Force | Out-String)
Try using one of two options:
use exit from your cmdlet, e.g. exit 0 (preferred).
use a global scope when setting the codes explicitly, E.g.
$global:LASTEXITCODE
I ran into this calling robocopy that sets non-zero exit codes even on success, and interfered with Jenkin's automation.

Powershell: How do I get the exit code returned from a process run inside a PsJob?

I have the following job in powershell:
$job = start-job {
...
c:\utils\MyToolReturningSomeExitCode.cmd
} -ArgumentList $JobFile
How do I access the exit code returned by c:\utils\MyToolReturningSomeExitCode.cmd ? I have tried several options, but the only one I could find that works is this:
$job = start-job {
...
c:\utils\MyToolReturningSomeExitCode.cmd
$LASTEXITCODE
} -ArgumentList $JobFile
...
# collect the output
$exitCode = $job | Wait-Job | Receive-Job -ErrorAction SilentlyContinue
# output all, except the last line
$exitCode[0..($exitCode.Length - 2)]
# the last line is the exit code
exit $exitCode[-1]
I find this approach too wry to my delicate taste. Can anyone suggest a nicer solution?
Important, I have read in the documentation that powershell must be run as administrator in order for the job related remoting stuff to work. I cannot run it as administrator, hence -ErrorAction SilentlyContinue. So, I am looking for solutions not requiring admin privileges.
Thanks.
If all you need is to do something in background while the main script does something else then PowerShell class is enough (and it is normally faster). Besides it allows passing in a live object in order to return something in addition to output via parameters.
$code = #{}
$job = [PowerShell]::Create().AddScript({
param($JobFile, $Result)
cmd /c exit 42
$Result.Value = $LASTEXITCODE
'some output'
}).AddArgument($JobFile).AddArgument($code)
# start thee job
$async = $job.BeginInvoke()
# do some other work while $job is working
#.....
# end the job, get results
$job.EndInvoke($async)
# the exit code is $code.Value
"Code = $($code.Value)"
UPDATE
The original code was with [ref] object. It works in PS V3 CTP2 but does not work in V2. So I corrected it, we can use other objects instead, a hashtable, for example, in order to return some data via parameters.
One way you can detect if the background job failed or not based on an exit code is to evaluate the exit code inside the background job itself and throw an exception if the exit code indicates an error occurred. For instance, consider the following example:
$job = start-job {
# ...
$output = & C:\utils\MyToolReturningSomeExitCode.cmd 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Job failed. The error was: {0}." -f ([string] $output)
}
} -ArgumentList $JobFile
$myJob = Start-Job -ScriptBlock $job | Wait-Job
if ($myJob.State -eq 'Failed') {
Receive-Job -Job $myJob
}
A couple things of note in this example. I am redirecting the standard error output stream to the standard output stream to capture all textual output from the batch script and returning it if the exit code is non-zero indicating it failed to run. By throwing an exception this way the background job object State property will let us know the result of the job.

Try method in powershell

So I want to build a try method into my powershell script below. If I am denied access to a server, I want it to skip that server. Please help..
[code]$Computers = "server1", "server2"
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers | Select-Object `
#{n='Server';e={ $_.__SERVER }}, `
#{n='Physical Memory';e={ "$('{0:N2}' -f ($_.TotalPhysicalMemory / 1024))mb" }}, `
#{n='Virtual Memory';e={ "$('{0:N2}' -f ($_.TotalPageFileSpace / 1024))mb" }} | `
Export-CSV "output.csv"[/code]
Try/catch functionality is built-into PowerShell 2.0 e.g.:
PS> try {$i = 0; 1/$i } catch { Write-Debug $_.Exception.Message }; 'moving on'
Attempted to divide by zero.
moving on
Just wrap you script in a similar try/catch. Note you could totally ignore the error by leaving the catch block empty catch { } but I would recommend at least spitting out the error info if your $DebugPreference is set to 'Continue'.
You can simply suppress errors with the ErrorAction parameter:
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers -ErrorAction SilentlyContinue | ...
You can use trap to replicate Try/Catch, see http://huddledmasses.org/trap-exception-in-powershell/ or http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx for examples.
Use a filter function? Like this tutorial explains.
He passes a list of computers to his pipeline - first it tries to ping each one, and then only passes the ones that respond to the next command (reboot). You could customize this for whatever actual functionality you wanted.