restart computer on all hosts at the same time - powershell

I have an idea about restarting all of host/VMs in my inventory.
I created some short script which restarting one by one of hosts.
But it's take to long time. How can i do that all hosts to be restarted at the same time?
My code:
foreach ($host in $hosts) {
Restart-Computer -ComputerName $host -Wait
Write-Host "$host restarted "
}

Restart-Computer will accept multiple entries for ComputerName so you can perform the restart on more than one host at a time.
Using this you can initiate the restart in batches, say three at a time, this will significantly decrease the time it takes without adding too much load on the VM Cluster/Host.
$hosts = #("server01","server02","server03","server04","server05","server06","server07","server08","server09","server10")
[int]$batches = 3 #number of computers to restart in each 'batch'
[int]$skip = 0
do {
$selected_hosts = $hosts | Select-Object -First $batches -Skip $skip
Restart-Computer -ComputerName $selected_hosts -Wait -WhatIf
Write-Host "$selected_hosts restarted"
$skip = $skip + $batches
}
while ($selected_hosts)
Note: Remove -WhatIf when you're ready to run the script live, with this in place Restart-Computer will just write to Console informing you what it would do and not actually performing the restart.

is it a good idea ?? - Restart-Computer -asJob
Or maybe are better solutions ??
$j = Restart-Computer -ComputerName "Server01", "Server02" -AsJob
PS C:\> $Results = $j | Receive-Job
PS C:\> $Results

Can you try WMI to reboot the servers. I found one such method on social.technet.microsoft Forum.
$server = get-content c:\Servers.txt
(gwmi -Class Win32_OperatingSystem -ComputerName $server).Win32Shutdown(6)
If ($?) {
Write-Host "$server successfully rebooted"
}Else{
Write-Host "Could not reboot $server"
}
You can find other methods also like using a batch file. Check out this link.

Related

Disable Print Spooler Script [duplicate]

I came across this one liner that appears to work:
stop-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
Can anyone explain why, because I thought stop-service didn't work unless you either used remoting or it occurred on the local host.
The output of Get-Service is a System.ServiceProcess.ServiceController .NET class that can operate on remote computers. How it accomplishes that, I don't know - probably DCOM or WMI. Once you've gotten one of these from Get-Service, it can be passed into Stop-Service which most likely just calls the Stop() method on this object. That stops the service on the remote machine. In fact, you could probably do this as well:
(get-service -ComputerName remotePC -Name Spooler).Stop()
Thanks to everyone's contributions to this question, I've come up with the following script. Change the values for $SvcName and $SvrName to suit your needs. This script will start the remote service if it is stopped, or stop it if it is started. And it uses the cool .WaitForStatus method to wait while the service responds.
#Change this values to suit your needs:
$SvcName = 'Spooler'
$SvrName = 'remotePC'
#Initialize variables:
[string]$WaitForIt = ""
[string]$Verb = ""
[string]$Result = "FAILED"
$svc = (get-service -computername $SvrName -name $SvcName)
Write-host "$SvcName on $SvrName is $($svc.status)"
Switch ($svc.status) {
'Stopped' {
Write-host "Starting $SvcName..."
$Verb = "start"
$WaitForIt = 'Running'
$svc.Start()}
'Running' {
Write-host "Stopping $SvcName..."
$Verb = "stop"
$WaitForIt = 'Stopped'
$svc.Stop()}
Default {
Write-host "$SvcName is $($svc.status). Taking no action."}
}
if ($WaitForIt -ne "") {
Try { # For some reason, we cannot use -ErrorAction after the next statement:
$svc.WaitForStatus($WaitForIt,'00:02:00')
} Catch {
Write-host "After waiting for 2 minutes, $SvcName failed to $Verb."
}
$svc = (get-service -computername $SvrName -name $SvcName)
if ($svc.status -eq $WaitForIt) {$Result = 'SUCCESS'}
Write-host "$Result`: $SvcName on $SvrName is $($svc.status)"
}
Of course, the account you run this under will need the proper privileges to access the remote computer and start and stop services. And when executing this against older remote machines, you might first have to install WinRM 3.0 on the older machine.
Based on the built-in Powershell examples, this is what Microsoft suggests. Tested and verified:
To stop:
(Get-WmiObject Win32_Service -filter "name='IPEventWatcher'" -ComputerName Server01).StopService()
To start:
(Get-WmiObject Win32_Service -filter "name='IPEventWatcher'" -ComputerName Server01).StartService()
This worked for me, but I used it as start. powershell outputs,
waiting for service to finshing starting a few times then finishes and then a get-service on the remote server shows the service started.
**start**-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
Another option; use invoke-command:
cls
$cred = Get-Credential
$server = 'MyRemoteComputer'
$service = 'My Service Name'
invoke-command -Credential $cred -ComputerName $server -ScriptBlock {
param(
[Parameter(Mandatory=$True,Position=0)]
[string]$service
)
stop-service $service
} -ArgumentList $service
NB: to use this option you'll need PowerShell to be installed on the remote machine and for the firewall to allow requests through, and for the Windows Remote Management service to be running on the target machine. You can configure the firewall by running the following script directly on the target machine (one off task): Enable-PSRemoting -force.
You can also do (Get-Service -Name "what ever" - ComputerName RemoteHost).Status = "Stopped"
You could just run a foreach and have logging enabled.
The console will show if something goes wrong and you can look in the log.
That way, you can then handle the errors individually.
I think it works better this way than running a Test-Netconnection for the verification part because firewall rules can create the value false.
For this example you ned a csv file with column ServerName, Populate the column with servername.contoso.com
$ServerList = "$PSScriptRoot\Serverlist.csv"
$Transcriptlog = "$PSScriptRoot\Transcipt.txt"
Start-Transcript -Path $Transcriptlog -Force
Get-Date -Format "yyyy/MM/dd HH:mm"
Try
{ # Start Try
$ImportServerList = Import-Csv $ServerList -Encoding UTF8 | ForEach-Object { # Start Foreach
New-Object PsObject -Prop #{ # Start New-Object
ServerName = $_.ServerName } # End NewObject
Invoke-Command -ComputerName $_.ServerName -ErrorAction Continue -ScriptBlock { # Start ScriptBlock
# Disable Service PrintSpooler
Get-Service -Name Spooler | Stop-Service -Force
} # End ScriptBlock
} # End Foreach
} # End Try
Catch
{ # Start Catch
Write-Warning -Message "## ERROR## "
Write-Warning -Message "## Script could not start ## "
Write-Warning $Error[0]
} # End Catch
Stop-Transcript
stop-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
This fails because of your variables
-ComputerName remotePC needs to be a variable $remotePC or a string "remotePC"
-Name Spooler(same thing for spooler)
As far as I know, and I cant verify it now, you cannot stop remote services with the Stop-Service cmdlet or with .Net, it is not supported.
Yes it works, but it stopes the service on your local machine, not on the remote computer.
Now, if the above is correct, without remoting or wmi enabled, you could set a scheduled job on the remote system, using AT, that runs Stop-Service locally.

Two Invoke-Command at the same time

I'm using this PowerShell script to resume Bitlocker on every active device:
Get-Content "clients.txt" | ForEach-Object {
if (Test-Connection $_ -Count 1 -ErrorAction 0 -Quiet) {
Invoke-Command -ComputerName $_ -ScriptBlock {
Resume-BitLocker -MountPoint "C:"
}
} else {
Write-Host "$_ is OFFLINE" -ForegroundColor Red
}
}
But I also want to trigger a hardware inventory via Invoke-WMIMethod on every active device with this command:
Invoke-WMIMethod -ComputerName $Server -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000001}"
I was able to script the first part but it isn't that well to built in the second command.
you are drifting a bit in the wrong direction.
When using Invoke-Command, it processes the scriptblock, against 32 computers simultaneously (in parallel)!
If you are processing computers with foreach, it would handle them sequentially, which would be much slower.
Same is valid when using *WMI cmdlets. Always try to replace them with the corresponding CIM cmdlets, as the same logic applies - computers are being processed in parallel.
Consider something like:
$ComputerList = Get-Content -Path Clients.txt
Invoke-Command -ComputerName $ComputerList -ErrorAction SilentlyContinue -ScriptBlock {
Resume-BitLocker -MountPoint "C:"
#Add second command
#add third command and so on
}
I am not sure, what would be the alternative command to Invoke-WMIMethod, when executing locally. Maybe Set-WMIInstance, but I am only speculating!
Then if you would like to add second command for execution, just add it into the scriptblock of Invoke-Command.

PowerShell Script to Uninstall multiple programs from a list of servers

I have written a basic PS script in order to uninstall any programs defined in a text file ($appname) on all servers defined in a text file ($servers).
Running this command manually without the variables it works fine, however running the script via Jenkins or from PS command line it just hangs so I can't even debug, anyone have any ideas?
[array]$servers= Get-Content "D:\Jenkins\BuildUtilities\Citrix\CitrixServerList.txt"
[array]$appname= Get-Content "D:\Jenkins\BuildUtilities\Citrix\ProgramsList.txt"
ForEach($server in $servers) {
$prod=gwmi -ComputerName $server Win32_product | ?{$_.name -eq $appname}
$prod.uninstall()
}
To clarify: By running manually I mean running the following:
gwmi -ComputerName CTX-12 Win32_product | ?{_.Name -eq "Microsoft Word"}
Microsoft Word is an example.
It should have been easy to follow Matts hints
[array]$servers= Get-Content "D:\Jenkins\BuildUtilities\Citrix\CitrixServerList.txt"
[array]$appname= Get-Content "D:\Jenkins\BuildUtilities\Citrix\ProgramsList.txt"
ForEach($server in $servers) {
$prod=gwmi -ComputerName $server Win32_product | ?{ $appname -contains $_.name}
$prod.uninstall()
}
You are doing an comparison of two arrays, what you want to do is check for every instance of matching applications in the prod array you fetch from the server, so using the pipeline how you have it is not ideal.
[array]$servers= Get-Content "D:\Jenkins\BuildUtilities\Citrix\CitrixServerList.txt"
[array]$appname= Get-Content "D:\Jenkins\BuildUtilities\Citrix\ProgramsList.txt"
$DebugPreference= 'Continue'
foreach($server in $servers)
{
Write-Debug "Getting all installed applications on server $server"
$prod= Invoke-Command -ComputerName $server -Scriptblock {gwmi Win32_product}
Write-Debug "Installed applications collected, there are $($prod.Count) items in the array."
foreach($p in $prod)
{
Write-Debug "Searching apps array for the name $($p.Name)."
if($appname -contains $p.Name)
{
Write-Verbose -Message "$($p.Name) found on server $server, uninstalling."
$p.uninstall()
}
else
{
Write-Verbose -Message "$($p.Name) was not found on server $server."
}
}
}
EDIT: Since you brought up the speed issue, using Win32_product is very slow. So the faster method would be to get a list of installed applications from the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall (details how to do that are here). Build a new array of applications found that need uninstalled and then call on Win32_product. Just know that this only speeds up one portion of your script, and if any application is found, the slow down will still occur. The major speed gains will come from when you find a server that doesn't have any applications to uninstall.
EDIT2: Did some experimenting, using a simple Invoke-Command greatly speeds up the Get-WMIObject command. I did it on Services, using the command alone took 9 seconds, using the Invoke-Command took 1 second. Test:
$firstStart = Get-Date
$service1 = Get-WmiObject win32_service -ComputerName $ServerName
$firstEnd = Get-Date
$firstTime = New-TimeSpan -Start $firstStart -End $firstEnd
Write-Host "The first collection completed in $($firstTime.TotalSeconds) seconds." -ForegroundColor Green
$secondStart = Get-Date
$service2 = Invoke-Command -ComputerName $ServerName -ScriptBlock {Get-WmiObject win32_service}
$secondEnd = Get-Date
$secondTime = New-TimeSpan -Start $secondStart -End $secondEnd
Write-Host "The second collection completed in $($secondTime.TotalSeconds) seconds." -ForegroundColor Green

PowerShell remote PC shutdown, single PC

OU=_ is a private company name. I know it's restart, this is only for testing before it goes into the real hutdown process.
function Get-LastBootUpTime {
param (
$ComputerName
)
$OperatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName
[Management.ManagementDateTimeConverter]::ToDateTime($OperatingSystem.LastBootUpTime)
}
$Days = -0
$ShutdownDate = (Get-Date).adddays($days)
$ComputerList = Get-ADComputer -SearchBase 'OU=TEST-OU,OU=_,DC=_,DC=_' ` -Filter '*' | Select -EXP Name
$ComputerList | foreach {
$Bootup = Get-LastBootUpTime -ComputerName $_
Write-Host "$_ last booted: $Bootup"
if ($ShutdownDate -gt $Bootup) {
Write-Host "Rebooting Computer: $_" -ForegroundColor Red
restart-Computer $Computer -Force
}
else {
Write-Host "No need to reboot: $_" -ForegroundColor Green
}
}
I'm trying to shutdown all of the PCs in my company that run longer than 2 days. The script is kind of done, but it shows an error when it comes to the point:
restart-Computer $Computer -Force
If I type instead of $Computer, $ComputerList the script shuts down every PC in that OU, even if they didnt run longer than 2 days.
So it only takes one PC to run longer than 2 days to shut down the entire company, and that's not what I want.
How can I tell the script to only turn the PCs off, when they have already run more than 2 days?
Your $Computer is not defined. You should use:
Restart-Computer $_ -Force
But the better approach would be to collect all of the computers that should restart in a variable and then restart them altogether. Would work much faster:
$toBeRestarted = $ComputerList | Where-Object { $ShutdownDate -gt (Get-LastBootUpTime -ComputerName $_) }
Restart-Computer $toBeRestarted -Force
You may add some more logging around if you like

Using Powershell to stop a service remotely without WMI or remoting

I came across this one liner that appears to work:
stop-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
Can anyone explain why, because I thought stop-service didn't work unless you either used remoting or it occurred on the local host.
The output of Get-Service is a System.ServiceProcess.ServiceController .NET class that can operate on remote computers. How it accomplishes that, I don't know - probably DCOM or WMI. Once you've gotten one of these from Get-Service, it can be passed into Stop-Service which most likely just calls the Stop() method on this object. That stops the service on the remote machine. In fact, you could probably do this as well:
(get-service -ComputerName remotePC -Name Spooler).Stop()
Thanks to everyone's contributions to this question, I've come up with the following script. Change the values for $SvcName and $SvrName to suit your needs. This script will start the remote service if it is stopped, or stop it if it is started. And it uses the cool .WaitForStatus method to wait while the service responds.
#Change this values to suit your needs:
$SvcName = 'Spooler'
$SvrName = 'remotePC'
#Initialize variables:
[string]$WaitForIt = ""
[string]$Verb = ""
[string]$Result = "FAILED"
$svc = (get-service -computername $SvrName -name $SvcName)
Write-host "$SvcName on $SvrName is $($svc.status)"
Switch ($svc.status) {
'Stopped' {
Write-host "Starting $SvcName..."
$Verb = "start"
$WaitForIt = 'Running'
$svc.Start()}
'Running' {
Write-host "Stopping $SvcName..."
$Verb = "stop"
$WaitForIt = 'Stopped'
$svc.Stop()}
Default {
Write-host "$SvcName is $($svc.status). Taking no action."}
}
if ($WaitForIt -ne "") {
Try { # For some reason, we cannot use -ErrorAction after the next statement:
$svc.WaitForStatus($WaitForIt,'00:02:00')
} Catch {
Write-host "After waiting for 2 minutes, $SvcName failed to $Verb."
}
$svc = (get-service -computername $SvrName -name $SvcName)
if ($svc.status -eq $WaitForIt) {$Result = 'SUCCESS'}
Write-host "$Result`: $SvcName on $SvrName is $($svc.status)"
}
Of course, the account you run this under will need the proper privileges to access the remote computer and start and stop services. And when executing this against older remote machines, you might first have to install WinRM 3.0 on the older machine.
Based on the built-in Powershell examples, this is what Microsoft suggests. Tested and verified:
To stop:
(Get-WmiObject Win32_Service -filter "name='IPEventWatcher'" -ComputerName Server01).StopService()
To start:
(Get-WmiObject Win32_Service -filter "name='IPEventWatcher'" -ComputerName Server01).StartService()
This worked for me, but I used it as start. powershell outputs,
waiting for service to finshing starting a few times then finishes and then a get-service on the remote server shows the service started.
**start**-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
Another option; use invoke-command:
cls
$cred = Get-Credential
$server = 'MyRemoteComputer'
$service = 'My Service Name'
invoke-command -Credential $cred -ComputerName $server -ScriptBlock {
param(
[Parameter(Mandatory=$True,Position=0)]
[string]$service
)
stop-service $service
} -ArgumentList $service
NB: to use this option you'll need PowerShell to be installed on the remote machine and for the firewall to allow requests through, and for the Windows Remote Management service to be running on the target machine. You can configure the firewall by running the following script directly on the target machine (one off task): Enable-PSRemoting -force.
You can also do (Get-Service -Name "what ever" - ComputerName RemoteHost).Status = "Stopped"
You could just run a foreach and have logging enabled.
The console will show if something goes wrong and you can look in the log.
That way, you can then handle the errors individually.
I think it works better this way than running a Test-Netconnection for the verification part because firewall rules can create the value false.
For this example you ned a csv file with column ServerName, Populate the column with servername.contoso.com
$ServerList = "$PSScriptRoot\Serverlist.csv"
$Transcriptlog = "$PSScriptRoot\Transcipt.txt"
Start-Transcript -Path $Transcriptlog -Force
Get-Date -Format "yyyy/MM/dd HH:mm"
Try
{ # Start Try
$ImportServerList = Import-Csv $ServerList -Encoding UTF8 | ForEach-Object { # Start Foreach
New-Object PsObject -Prop #{ # Start New-Object
ServerName = $_.ServerName } # End NewObject
Invoke-Command -ComputerName $_.ServerName -ErrorAction Continue -ScriptBlock { # Start ScriptBlock
# Disable Service PrintSpooler
Get-Service -Name Spooler | Stop-Service -Force
} # End ScriptBlock
} # End Foreach
} # End Try
Catch
{ # Start Catch
Write-Warning -Message "## ERROR## "
Write-Warning -Message "## Script could not start ## "
Write-Warning $Error[0]
} # End Catch
Stop-Transcript
stop-service -inputobject $(get-service -ComputerName remotePC -Name Spooler)
This fails because of your variables
-ComputerName remotePC needs to be a variable $remotePC or a string "remotePC"
-Name Spooler(same thing for spooler)
As far as I know, and I cant verify it now, you cannot stop remote services with the Stop-Service cmdlet or with .Net, it is not supported.
Yes it works, but it stopes the service on your local machine, not on the remote computer.
Now, if the above is correct, without remoting or wmi enabled, you could set a scheduled job on the remote system, using AT, that runs Stop-Service locally.