I am working on a script that will connect to a list of servers, some of the servers would have issues such as being offline, winRM issues, general server health etc. I would like to attempt connection to the host, and have a timeout for winrm connectivity issues.
So far I have come up with the script below.
$list = #('server1','server2','server3')
$session = New-PSSession -ComputerName $list -ErrorAction SilentlyContinue -credential Get-Credential
$timeout = 30
$remote = Invoke-Command -Session $session -ScriptBlock {[System.Net.Dns]::GetHostName()} -AsJob
$remote | Wait-Job -Timeout $timeout
$output = $remote | Receive-Job
foreach ($server in $output)
{
invoke-sqlcmd -ServerInstance "servername" -Query "insert into mytable select '$server'"
}
What is the problem.
In the example above, I have no way of tracking servers that failed and what ends up happening is that I can see my list has server1, server2 and server3, if after checking my database I only find server1 and server2, I have to do a diff to work out that server3 must have timed out or failed.
What am i trying to achieve.
To have a script that can deal with thousands of servers, run a simple command on each host and return back the result, for servers that timeout or experience issues, log the particular server, log a progress log from the array list and as a bonus, if the script can run in parallel to speed up the progress of the execution.
Related
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.
I'm researching whether it's possible to host an always-running Powershell remoting process.
By default, PSRemoting will start a process as the authenticated user.
However, using custom PS Session Configurations, it's possible to set a "run as account" that will ensure that the remote process (on the server, so to speak) always runs as a "known" user, regardless of the identify of the connecting client.
The reason for me researching this, is that I notice that Ansible runs quite slowly against windows servers, and I suspect this is due to the fact that the remoting process on the server gets spun up with each command Ansible sends. I'd like to see if it's possible to have an always-running process that is "ready" in order to speed up executions.
This is as far as I've gotten:
$ansiblecred = get-credential
New-PSSessionConfigurationFile `
-path "C:\sessionconfig.pssc" -SessionType Default `
-RequiredGroups #{ And = 'Administrators' }
Unregister-PSSessionConfiguration -Name ansible -force
Register-PSSessionConfiguration `
-Path "C:\sessionconfig.pssc" -Name ansible `
-RunAsCredential $ansiblecred -AccessMode Remote `
-UseSharedProcess -ThreadOptions ReuseThread
restart-service winrm
$remotecred = Get-Credential
$i = 0
while ($i -lt 10)
{
#This is slow because the remoting session is setup/teard down every time
Invoke-Command -ComputerName "localhost" -Credential $remotecred -Authentication Basic -ScriptBlock {$env:computername} -ConfigurationName ansible
$i ++
}
Even tho I'm connecting to the session with a different credential, the actual process runs as the "service" credential, so that part's good.
However, it seems to be still spinning up and down the process on each execution.
Just for clarification: The client here is not regular Powershell, it's client which will interact directly with the wsman service over http. So while I appreciate all responses, suggestions based around client-side Powershell code (such as new-pssession, invoke-command etc) are not gonna help :-|
Any pointers would be appreciated here, I'm trying to get to a place where the remoting process simply lives regardless of sessions being executed or not. Is this possible?
Create a session outside and use it in the loop.
$Session = New-PSSession -ComputerName $Server -ConfigurationName MyConfiguration
While(1){
Invoke-Command -Session $Session -Credential $remotecred -Authentication Basic -ScriptBlock {'my code'}
sleep 10
}
When running the below code, i can put anything in the block at the bottom - I'm trying to copy a folder across to run an exe from a local folder and perform an install of that exe during the remote session to remote machines. I am getting Access Denied Errors. I read, i cant use the Kerberos Delegation Cmdlets which are only for a forest level of 2012 and above. Current Site has Domain Functional Level 2008 R2. Is there another way to achieve copying the files across during each remote session to the computers specified in the text file?
Thanks in advance
########################################
$Cred = Get-Credential DOMAIN\USER
$Computers = Get-Content C:\tab.txt | Where-Object { $_ }
ForEach ($Computer in $Computers)
# {
# if (Test-Connection -ComputerName $Computer -BufferSize 16 -Count 1 `
-Quiet)
{
# Creates a new remote PowerShell Session and script block - enter
the code you want to execute remotely from this block
$Session = New-PSSession $computer -Credential $cred
Invoke-Command -Session $Session -ScriptBlock {
Copy-Item -Path "\\print-server\pcclient\win\*" -Destination
"c:\pcclient" -Force -Recurse -Verbose
# Start-Sleep -s 10
# Start-Process "\\Print-Server\PCClient\win\client-local-install.exe" -ArgumentList "/SILENT"
}
}
Remove-PSSession -Session $Session
# }
This is because you're on a remote machine, trying to access another network resource. When you connect to the remote machine in PowerShell, you're effectively connected/authenticated to that machine only, (unless you specify otherwise) it doesn't have access to your credentials to access the network share, so the connection to the network share is treated as unauthenticated, hence the failure.
This article https://blogs.technet.microsoft.com/heyscriptingguy/2012/11/14/enable-powershell-second-hop-functionality-with-credssp/ covers it well, essentially in you will need to run this locally (to allow your machine to pass credentials):
Enable-WSManCredSSP -Role Client -DelegateComputer * -Force
On the server run (to allow the server to accept these credentials):
Enable-WSManCredSSP -Role Server –Force
And update your New-PSSession command to:
$Session = New-PSSession $computer -Credential $cred -Authentication CredSSP
If you want, you can share your credentials with only specific machines, or subsets of a domain using *.yourdomain.lan or whatever, if you connect to multiple machines, then it's easier to use -DelegateComputer *.
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!
I am trying to get the mibinfo for multiple dhcp servers in our infrastructure. My problem is that when i run the command
invoke-command -computername $dhcpserver -credential $Cred -scriptblock{netsh dhcp server show mibinfo}
I get MIBinfo for all the servers, but i don't get the name of the server in the output. So i need a way where i get the output as
Server1
mibinfo
server2
mibinfo
($dhcpserver has the list of all the dhcp servers.)
Is $dhcpserver an array of strings? I didn't know Invoke-Command could do this...
If so, try this:
Foreach ($server in $dhcpserver) {
$mibOutput = invoke-command -computername $server -credential $Cred -scriptblock{netsh dhcp server show mibinfo}
Write-Output "$server $mibOutput"
}