PowerShell remote session and Start-Job issue - powershell

I am trying to run following script with job but the code in the block only executes 1st command and exits. Job is displayed completed on my computer
$computers = get-adcomputer -filter * | where { ($_.DNSHostName -match 'server')}
foreach ($computer in $computers) {
$session = New-PSSession -ComputerName $computer.DNSHostName
Invoke-Command -Session $session -ScriptBlock {
Stop-Service W3SVC -Force
Remove-Item "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root" -Force -Recurse
Start-Service W3SVC
} -asjob -jobname IIS_Maintenance
Remove-PSSession -Session $session
}
If I comment out -asjob -jobname IIS_Maintenance job runs fine but it's synchronous. It takes several seconds to stop IIS but I am not sure why job is not waiting on that.
Thoughts?

Creating the session induces a lot of delay. Why did you chose to use the -Session instead of directly using -ComputerName ?
I think using the -ComputerName way will be more efficient to run with Start-Job. Either way, Since you are invoking the jobs on remote machines, your workstation would have no clue on the progress of the jobs.
If you want to track the progress of the jobs, you shouldn't be using Invoke-Command at all.

OK I figured it out... the point of confusion was:
1) I didn't know I can send block of code without Session and yes session implementation is slow
2) I also didn't know I send invoke-command to several computers are once without foreach. Thank you for that Ansgar Wiechers!

Related

Issue getting pssessions to loop properly for multiple servers listed in a txt file

I start with a txt file named vms.txt.
It contains 2 servers like so:
server1
server2
When I run the script shown below, the command that is invoked to install VMware tools only runs on server2 and server1 gets skipped. Does anyone have any suggestions on how to modify this script to make it run on both servers in the txt file? I will have to run this in the future for hundreds of VMs, so I am trying to find an easy way to get this to loop properly.
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
foreach($vm in $vms){
$sessions = New-PSSession -ComputerName $vm -Credential $cred
}
foreach($session in $sessions)
{
Invoke-Command -Session $session -ScriptBlock {
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI”
}
}
In your loop-based approach, the problem is your variable assignment:
# !! This only ever stores the *last* session created in $sessions,
# !! because the assignment is performed in *each iteration*.
foreach($vm in $vms){
$sessions = New-PSSession -ComputerName $vm -Credential $cred
}
The immediate fix is to move the assignment out of the loop:
# OK - captures *all* session objects created in $sessions
$sessions = foreach($vm in $vms){
New-PSSession -ComputerName $vm -Credential $cred
}
Taking a step back:
Both New-PSSession -ComputerName and Invoke-Command -Session accept an array of computer names / sessions, so there's no need for loops.
Passing multiple sessions / computer names to Invoke-Command has the big advantage that the operations run in parallel.
Note:
Invoke-Command has built-in throttling to avoid targeting too many machines at once. It defaults to 32, but can be modified with the -ThrottleLimit parameter.
Output from the targeted computers will arrive in no predictable order, but the output objects are decorated with (among others) a .PSComputerName property reflecting the originating computer - see the bottom section of this answer.
That is, your code can be simplified to:
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
$sessions = New-PSSession -ComputerName $vms -Credential $cred
Invoke-Command -Session $sessions -ScriptBlock {
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI”
}
Important:
Sessions should eventually be cleaned up with Remove-PSSession when no longer needed.
However, that stops any commands running in those sessions, so if you've launched asynchronous operations via your Invoke-Command call, you need to ensure that those operations have finished first - see the comments re potentially asynchronous execution of your VMware-tools-10.3.5.exe application below.
Or, even simpler, if you only need to execute one command on each machine, in which case there is no need to create sessions explicitly, pass all computer names directly to Invoke-Command's -ComputerName parameter:
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
# Note the use of -ComputerName
Invoke-Command -ComputerName $vms -Credential $cred -ScriptBlock {
# Note the use of | Write-Output to ensure synchronous execution.
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI” | Write-Output
}
Important:
If your application (VMware-tools-10.3.5.exe) runs asynchronously, you must ensure its
synchronous execution, otherwise it may not run to completion, because the implicitly created remote session is discarded when a script block returns from a given computer.
A simple trick to ensure synchronous execution of any external (GUI-subsystem) executable is to pipe it to Write-Output, as shown above (or Wait-Process, if it doesn't produce console output) - see this answer for an explanation.

How to Properly Put together a Function in Powershell

I have done a ton of research on how to push this EXE to a remote PC using PSSession and all lines of code work when executed line by line. But i have had a hard time putting this into a function that makes sense and will execute all lines of code and successfully install the software with one push of a button. Not sure whats happening. It will install the exe locally when i tried to do put all lines of code in a function and run it. Can you please help instruct what i am doing wrong? Sorry i am a newbie at Powershell.
$dc1 = New-PSSession -ComputerName DC1
Copy-Item C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe -Destination C:\TPAdmin -ToSession $dc1
Enter-PSSession -Session $dc1
Invoke-Command -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"
Remove-Pssession $dc1
Enter-PSSession is for interactive use only, so not suitable for use in a function.[1]
Instead of using Enter-PSSession, pass the session you've created with New-Session to the Invoke-Command command's -Session parameter, which will run the command in the context of that (remote) session.
# Define your function...
function Invoke-InstallerRemotely {
param([string] $ComputerName)
$session = New-PSSession -ComputerName $ComputerName
Copy-Item C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe -Destination C:\TPAdmin -ToSession $session
# Do NOT use Enter-PSSession.
# Pass your session to Invoke-Command with -Session
Invoke-Command -Session $session -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"
Remove-PSSession $session
}
# ... then invoke it.
# Note that functions must be defined *before* they're called.
Invoke-InstallerRemotely -ComputerName DC1
[1] Using it in a function means that an interactive session on the target computer is entered, which you must exit interactively (by typing and submitting exit or Exit-PSSession) before the remaining statements in the function are executed, again locally.
As for …
Sorry i am a newbie at Powershell.
… that's all fine, as we all had to start from somewhere. However... a couple of things here:
Please be sure to format your posts, to encourage folks to want to
help. People frown on no doing that. Having to copy, paste and
reformat your post is well, extra unnecessary work. ;-}. We've al been there.
We have no idea how you are getting up to speed on PowerShell, but
use the freely available resources to limit/avoid all the
misconceptions, frustrations, errors, potential bad habits, etc.,
that you are going to encounter. Live on watching the videos on:
YouTube
Microsoft Virtual Academy
MSDN Channel9
Microsoft Learn
as well as the reference
and eBook resources.
Back to your use case. You do not say what is happening. So, you leave us to guess. Which is not really potentially helpful to you.
Nonetheless, you should just need to do this... in PowerShell v5x as it's require to use the -ToSession argument.
$DC1 = New-PSSession -ComputerName 'DC1'
Copy-Item -ToSession $DC1 -Path 'C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe' -Destination 'C:\TPAdmin'
Invoke-Command -Session $DC1 -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"}
Remove-PSSession -Session $DC1
I am not sure why you are doing that Enter-PSSsssion in the New-PSSession command as it is not needed. It's for standalone interactive sessions.
Explicit PSRemoting = Enter=PSSEssion
Implicit PSREmoting = New-PSSEssion
If all else fails for you on the copy via the session, then just use the normal UNC way to copy from source to destination.
Copy-Item -Path 'C:\temp\Results.csv' -Destination "\\$($DC1.Computername)\c$\temp"
See also:
Copy To or From a PowerShell Session

PowerShell start-sleep cmdlet

Hey I am very new to PowerShell and found one of Ed Wilson's helpful scripts on his blog: http://blogs.technet.com/b/heyscriptingguy/archive/2012/11/12/force-a-domain-wide-update-of-group-policy-with-powershell.aspx.
I needed to customize it a little for my needs and just need some help getting the right code down.
I will just use his code because all I did was replace it with my credentials and AD info:
$cn = Get-ADComputer -filt *
$cred = Get-Credential iammred\administrator
$session = New-PSSession -cn $cn.name -cred $cred
icm -Session $session -ScriptBlock {gpupdate /force}
What I added was the next two lines to attempt to pause the script to allow the gpupdate to process then restart the computer(s):
Start-Sleep -s 120
Restart-Computer -ComputerName $cn.name
When I run the script all together it seems to just hang after I enter my credentials. My guess would be it doesn't like the way I present the Start-Sleep cmdlet because I can run the Restart-Computer cmdlet with success without Start-Sleep. The problem is it doesn't wait for gpupdate to finish so the policy doesn't get pushed. I do not get any errors when I run the script, it just hangs. I have left it running for about 10 minutes with no success.
I appreciate any help or suggestions to make this script work properly. Thanks in advance for any input.
There's nothing wrong with your sleep invocation but it isn't the best way to go. You can wait on the job to finish with a timeout (in case the command hangs) e.g.:
$job = icm -Session $session -ScriptBlock {gpupdate /force} -AsJob
Wait-Job $job -Timeout 120
Remove-PSSession $session
Restart-Computer $cn.name
#Keith Hill is right, sleep looks good. Another thing you can do is push the sleep/restart commands onto your target machines:
icm -Session $session -ScriptBlock {
gpupdate /force; Start-Sleep -s 120; Restart-Computer -Force}

Prevent additional windows from displaying in powershell script

The idea here is to uninstall a program silently. Unfortunately the program's msi is custom and executes a batch file during the uninstall process. What I am trying to do is prevent this window from displaying during the powershell uninstall script. I'm using wmi uninstall method because the msi that the program uses doesn't respond to quite or uninstall flags (/q, /x, /uninstall). I have attempted to run it as a background job but the window still appears. The script looks as follows:
start-job -scriptblock `
{(Get-WmiObject -class Win32_Product |? {$_Name -eq "Annoying1"}).uninstall()} `
-Name Uninstall1
$job = get-job -name Uninstall1
wait-job $job
receive-job $job
This will run mostly hidden until the uninstall job gets to the point where the batch file is executed, at which point a cmd window will appear and run. Any ideas of how to run this script without displaying extra windows?
The script is ran with -windowstyle hidden as well.
Be indulgent, I'am not proud of this, but I'am quite sure that if you try to localy run your script using remoting the window will not be seen by the user :
I just try :
$sess = New-PSSession -ComputerName localhost
Invoke-Command -Session $sess -ScriptBlock {calc.exe}
get-process "calc"
In your case try :
$sess = New-PSSession -ComputerName localhost
$job = Invoke-Command -Session $sess -ScriptBlock {(Get-WmiObject -class Win32_Product |? {$_Name -eq "Annoying1"}).uninstall()} -asjob -Name Uninstall1
wait-job $job
receive-job $job

New-PSSession in Job blocks Job

I need to start PSSession in job. But I can't use Invoke-Command -AsJob due product architecure.
I tried something like:
$block {
"Job started"
$session = New-PSSession -computername "host-name"
"Session started"
}
$job = Start-Job -ScriptBlock $block
Wait-Job -Job $job -Timeout 10 | Out-Null
Receive-Job -Job $job
(I don't have PS on system I post this question, so I could make mistake in syntax, skip it, please)
But, instead of creating remote session job became blocked forever on New-PSSession line.
Wait-Job will exit by timeout, and Recieve-Job will return only "Job started".
I tried:
I ensure that remoting in main thread is working well, and Invoke-Command -AsJob is working too.
Invoke-Command and other PSSession-based cmdlets have same behavior — blocks job execution.
Create PSSession in main thread and then transfer session object or object.Id as -InputArguments for job
Result: PSSession created in main thread can't be used in child Job, no matter, was it transfered or found inside job`s scriptblock by Get-PSSession
Thanks in advance!