Redirect Errors From Invoke-Command - powershell

I have a script that manages tasks across hundreds of virtual servers, it runs Invoke-Command in a job and outputs the details to a web page.
My problem is errors thrown within the Invoke-Command scriptblock are not piped:
This command correctly pipes the error (cannot connect to server) and outputs as a string:
Invoke-Command [dead server] -ScriptBlock { Write-Error "Test" } *>&1 | Out-String
This command seems to ignore the error completely, it is not displayed at all:
Invoke-Command [live server] -ScriptBlock { Write-Error "Test" } *>&1 | Out-String
This command correctly pipes the error out as a string:
Invoke-Command [live server] -ScriptBlock { Write-Error "Test" *>&1 | Out-String }
Using both the first and third examples I can pipe everything out, but it's not that simple. I will probably need to run complex scripts using this and it's unreasonable to expect me to redirect every single command so that errors are picked up.
I can't even find anything to wrap it in to pipe everything out.
Actually a function would work but it seems a very roundabout solution as there doesn't seem to be a way to convert a scriptblock to a function, so I'd have to put the scriptblock in an Invoke-Command in a Function in an Invoke-Command, which is in a job in a PSSession...

There is some code on GitHub PowerShell\Remotely which I believe does what you want.
The code does this:
First in call Invoke-Command as a job and waits for the job, like this:
$testjob = Invoke-Command -Session $sessions -ScriptBlock $test -AsJob -ArgumentList $ArgumentList | Wait-Job
Then it constructs a result object based on the job, giving you each of the stream (most of them at least.) This is the complicated code.
The code more than likely would need to be refactored for your purposes as it is intended for testing.
If you have the time, you should be able to refactor it to meet you needs. The code, as is, gives you the streams as properties of an object, but you should be able to pipe those objects to where you want them to go (you just have to remember to check each of them.)

Related

Using Parameters when Calling Script with Start-Job

I am working on a script that must change users in the middle of running in order to be able to access a network folder. I have figured out how to get the credentials working, but now cannot understand how to pass parameters to the second script that is being called. The code that I currently have:
$myJob = Start-Job -ScriptBlock {& "\\my\folder\path\script.ps1" -serverName $serverName -serverInstance $serverInstance} -Credential $cred
$myJob | Wait-Job
$myJob | Receive-Job -Keep
I need to pass the serverName and serverInstance variables to the script that Start-Job is running, while also still being able to use credential. Is there a way to do this?
I have investigated Invoke-Command and Invoke-Expression, but neither of those fit this situation. Invoke-Command doesn't work with remote computers/drives and Invoke-Expression doesn't work with credentials. I tried the answer that was provided here, but that would not correctly pass in the parameters either.
Any help is much appreciated as I have been working on this problem for a few hours now. I am sure I am missing something obvious.
You can use the using scope modifier provided you are on PowerShell version 3 or higher:
$myJob = Start-Job -ScriptBlock {& "\\my\folder\path\script.ps1" -serverName $using:serverName -serverInstance $using:serverInstance}
You can also use local variables in remote commands, but you must indicate that the variable is defined in the local session. Beginning in Windows PowerShell 3.0, you can use the Using scope modifier to identify a local variable in a remote command. The syntax of Using is as follows:
$Using:<VariableName>
If you are on PowerShell version 2, you will need to utilize the -ArgumentList parameter and then modify your scriptblock to accept the arguments that are passed. Avshalom comments on one way to do this.
See About_Remote_Variables for more information.

Powershell Invoke-GPUpdate - "No Logoff" possible?

Looking to run Invoke-GPUPdate -force to a group of remote computers and respond to the logoff prompt with "No".
Tried:
Echo "n" | invoke-gpupdate
Error:Invoke-gpupdate does not accept pipeline input
Command Used:
Invoke-GPUpdate -Computer $computer -RandomDelayInMinutes 0 -force
Unfortunately it looks like this cmdlet initiates/schedules a run of gpupdate that ends up happening separately (out of process), so there isn't much to do via PowerShell's standard ways of dealing with something like that, since the prompt doesn't come from within PowerShell. There's a -LogOff parameter, but it's a switch parameter which implies that its value is meant to be used just for doing the logoff. You can try it this way: -Logoff:$false but most likely it won't work to get rid of the prompt.
I think your best chance is not to use this cmdlet, but to instead use Invoke-Command with gpupdate.exe directly:
Invoke-Command -ComputerName $computer -ScriptBlock {
echo nn | gpupdate.exe /force
}
But this requires that PowerShell remoting is enabled on the machines you want to manage.

Powershell build step, fire and forget?

I am running the following powershell command in a build step using TFS 2018.
Start-Job -ScriptBlock {
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
}
Since I don't want the script to affect the build step it should simply fire and forget the script. Hence I am using Start-Job. But it seems that once the step is done the process is killed. Is there a way to maintain the process lifetime even though the build step is done or the build process is finished?
Additional information... the powershell script should run on the remote server. The script itself triggers an .exe with parameters.
To simply fire and forget, invoke the script with Invoke-Command -AsJob:
Invoke-Command -AsJob -FilePath \\MyServer\run.ps1 -ComputerName MyServer -Args arg1, arg2
Start-Sleep 1 # !! Seemingly, this is necessary, as #doorman has discovered.
This should kick off the script remotely, asynchronously, with a job getting created in the local session to monitor its execution.
Caveat: The use of Start-Sleep - possibly with a longer wait time -
is seemingly necessary in order for the remote process to be created before the calling script exits, but such a solution may not be fully robust, as there is no guaranteed timing.
Since you're not planning to monitor the remote execution, the local session terminating - and along with it the monitoring job - should't matter.
When do you want the script to stop running? You could use a do-while loop and come up with a <condition> that meets your needs.
Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while(<condition>)
}
Alternatively, you could use the condition $true so it executes forever. You will have to stop the job later in the script when you no longer need it.
$job = Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while($true)
}
Stop-Job $job
Remove-Job $job
I've added a Start-Sleep 2 so it doesn't lock up your CPU as no idea what the script is doing - remove if not required.
Why not something like this:
Invoke-Command -Filepath \\MyServer\Run.ps1 -Computername MyServer -Argumentlist Arg1,Arg2 -AsJob
$JobCount = (get-job).Count
Do
{
Start-Sleep -Seconds 1
$totalJobCompleted = (get-job | Where-Object {$_.state -eq "Completed"} | Where-Object {$_.Command -like "NAMEOFCOMMAND*"}).count
}
Until($totalJobCompleted -ge $JobCount)
#doorman -
PowerShell is natively a single threaded application. In almost all cases, this is a huge benefit. Even forcing multiple threads, you can see the child threads are always dependent on the main thread. If this wasn't the case, it would be very easy to create memory leaks. This is almost always a good thing as when you close the main thread, .Net will clean up all the other threads you may have forgotten about for you. You just happened to run across a case where this behaviour is not beneficial to your situation.
There are a few ways to tackle the issue, but the easiest is probably to use the good ol' command prompt to launch an independent new instance not based at all on your original script. To do this, you can use invoke-expression in conjunction with 'cmd /c'. See Below:
invoke-expression 'cmd /c start powershell -NoProfile -windowstyle hidden -Command {
$i = 0
while ($true) {
if($i -gt 30) {
break
}
else {
$i | Out-File C:\Temp\IndependentSessionTest.txt -Append
Start-Sleep -Seconds 1
$i++
}
}
}
'
This will start a new session, run the script you want, not show a window and not use your powershell profile when the script gets run. You will be able to see that even if you kill the original PowerShell session, this one will keep running. You can verify this by looking at the IndependentSessionTest.txt file after you close the main powershell window and see that the file keeps getting updated numbers.
Hopefully this points you in the right direction.
Here's some source links:
PowerShell launch script in new instance
How to run a PowerShell script without displaying a window?

Invoke-Command write file to parent machine

I have a script that connects to a remote machine using Invoke-Command and then runs a script block. This all works fine, however I would like to know if its possible to write a log file on to the parent machine. Due to firewalling on the servers the parent can connect to the child, but the child cannot connect to the parent.
At the moment when I use Out-File it creates that file on the server that I am connected to. I would like to find a way to create that file, and append to it on the server that I am connecting from.
Output in the scriptblock is returned to the caller. Simply pipe or redirect the output of Invoke-Command to a file.
Invoke-Command -Computer ... -ScriptBlock {...} > 'C:\path\to\out.txt'
or
Invoke-Command -Computer ... -ScriptBlock {...} | Out-File 'C:\path\to\out.txt'

Start-job vs. Invoke-command -asjob

I'm trying to do basic background jobs in PowerShell 2.0, and I'm seeing different things with start-job and invoke-command -asjob.
If I do this:
start-job -scriptblock {get-process}
I get a job object, but the child job (which is created automatically by start-job) always has a JobStateInfo of "NotStarted".
this, however, works as expected:
invoke-command -scriptblock {get-process} -computer localhost -asjob
I've run the enable-psremoting....anything else I need to do to get background jobs working?
The first example using start-job does not use HTTP for the call and instead uses an IPC channel with WinRM to run; it does not require administrative privileges this way. The second example with invoke-command does require admin rights (by default) and will connect via HTTP and WinRM.
To be honest, I would have expected the second one to fail for most people. If you run: Receive-Job against the ID of the start-job invocation, do you get any error messages?
-Oisin
To receive an updated JobStateInfo you'll need to use Get-Job and the job created by Start-Job. Though, if you're using this information to see when the job finishes, Wait-Job or Receive-Job -wait might be better suited to your needs.
Wait-Job simply waits until the job, or list of jobs, indicated is finished before moving on. Receive-Job -wait does the same thing, but it also gathers the results/output of the job.