PowerShell remote PC shutdown, single PC - powershell

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

Related

ForEach Loop failing to continue on the file?

The ForEach loop on this powershell script is failing to run more than one item before dropping out?
Can someone help me on this one?
function Get-RemoteLogonStatus {
[CmdletBinding()]
param(
[string]$ComputerName = ' '
)
ForEach ($line in Get-Content C:\ADComputers.csv)
{
$Computername = $line
if ( Test-Connection -ComputerName $ComputerName -Count 3 -Quiet ) {
try {
Get-WmiObject –ComputerName $ComputerName –Class Win32_ComputerSystem | Select-Object UserName = $lname -ErrorAction Stop | Out-Null
}
catch {
Write-Output 'No user logged in - RESTARTING.'
Shutdown /r /t 0 /M \\$ComputerName
$ComputerName
return
}
Write-Output 'Computer in use.'
$ComputerName
}
else {
Write-Output 'Computer in Use or is Offline.'
$ComputerName
}
}
$error.clear
}
Get-RemoteLogonStatus
Should run more than one item from the file. The file has 4 items for test:
a function is supposed to contain a block of code that can be repeated a number of times. Your function does all in one go, hence I don't see the need for it. Also it has the possibility to take one argument, but you don't pass it.
'return' is not necessary in PowerShell, it will throw the content of a variable without the need for a 'return'.
Select-Object needs a name that is being passed from the pipe, and not an assignment.
inside the try statement you might want to get an output, but if you pipe the line to Out-null you get nothing. and the catch will never grab any error.
the write-output are not clearly positioned, and difficult to understand.
I can infer what you are trying to achieve is: reboot computers in the csv file IF no user is logged in, is that so? In that case it's much simpler:
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
of if you want to stick to a function and see its purpose see this:
function Restart-Node {
param(
[Parameter(Mandatory=$true)][string]$ComputerName
)
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
Restart-Node -ComputerName $ComputerName
}

Powershell script to compare AD computers to text file and change registry service on those computer and then write the computers that were offline

I'm trying to filter out computers that have already ran the script (that enables remote registry service) within AD from a list in a text file
$NamesFromFile = Get-Content
C:\scripts\Inventory\offlineRemoteRegStartupWorkstations.txt
$computers = get-adcomputer -Filter * | Where-Object {
$_.Name.SubString(1) -in $NamesFromFile }
foreach ($computer in $computers)
{
if (Test-Connection -count 1 -computer $computer.Name -quiet){
Write-Host "Updating system" $computer.Name "....." -ForegroundColor
Green
Set-Service –Name remoteregistry –Computer $computer.Name -StartupType
Automatic
Get-Service remoteregistry -ComputerName $computer.Name | start-service
}
else
{
Write-Host "System Offline " $computer.Name "....." -ForegroundColor Red
echo $computer.Name >> C:\scripts\Inventory\offlineRemoteRegStartup.txt}
}
no errors just blank
Below a re-write of your script.
Because you test if only the first character of the computer name is in the list of computers, your $computers variable will remain empty, so nothing happens.
Also, I think it would be wise to add a check if the startup type of the RemoteRegistry service is not already set to Automatic, because after all.. the computernames you read in from the file may not be accurate.
To avoid having to use $computer.Name all the time, I use a Select-Object -ExpandProperty Name, so we only have to run through a list of strings.
$NamesFromFile = Get-Content -Path 'C:\scripts\Inventory\offlineRemoteRegStartupWorkstations.txt' | Sort-Object -Unique
Get-ADComputer -Filter * |
Where-Object { $NamesFromFile -contains $_.Name } | # if the computer name is in the list
Select-Object -ExpandProperty Name | # we're only interested in the Name property
ForEach-Object {
# the automatic variable '$_' represents a single computername from the list
if (Test-Connection -Count 1 -ComputerName $_ -Quiet) {
# test if the RemoteRegistry service startup type is not already Automatic
# you can do the same with (Get-WmiObject -Class Win32_Service -Filter "Name='RemoteRegistry'" -ComputerName $_)
# only slower..
if ((Get-CimInstance -Class Win32_Service -Filter "Name='RemoteRegistry'" -ComputerName $_).StartMode -ne 'Auto') {
Write-Host "Updating system '$_'....." -ForegroundColor Green
Set-Service –Name RemoteRegistry –Computer $_ -StartupType Automatic
Get-Service -Name RemoteRegistry –Computer $_ | Start-Service
}
else {
Write-Host "RemoteRegistry service startup type already Automatic on computer '$_'....." -ForegroundColor Yellow
}
}
else {
Write-Host "System Offline '$_'....." -ForegroundColor Red
Add-Content -Path 'C:\scripts\Inventory\offlineRemoteRegStartup.txt' -Value $_
}
}

restart computer on all hosts at the same time

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.

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

Shutting down multiple PCs remotely

i want to shut down almost all PCs at my workplace (if they run more than 2 days)
I've worked the last and this week on a Script and trying to get rid of Errors on the way.
$days = -0
$date = (get-date).adddays($days)
$lastboot = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$Computer = Get-ADComputer -SearchBase 'OU=______,OU=______,DC=______,DC=______' ` -Filter '*' | Select -EXP Name
$lastbootconverted = ([WMI]'').ConvertToDateTime($lastboot)
write-host $date
write-host $lastboot
write-host $lastbootconverted
if($date -gt $lastbootconverted)
{
write-host Need to reboot
(Stop-Computer -$Computer -Force)
}
else
{
write-host no need to reboot
}
When I run it it says
"The RPC-Server isn't available. (Exception HRESULT: 0x800706BA)"
But if I just put a PC Name instead of the "$Computer", it shuts the PC down like I want. What is this RPC-Server Error? I don't have a firewall activated, so I'm clueless...
The OU=_____ and DC=______ are private company names
I've got not AD environment to test your Get-ADComputer query, but this worked for me with just an array of computer so should be fine for you.
function Get-LastBootUpTime {
param (
$ComputerName
)
$OperatingSystem = Get-WmiObject Win32_OperatingSystem -ComputerName $ComputerName
[Management.ManagementDateTimeConverter]::ToDateTime($OperatingSystem.LastBootUpTime)
}
$Days = -2
$ShutdownDate = (Get-Date).adddays($days)
$ComputerList = Get-ADComputer -SearchBase '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 $_ -Force
}
else {
Write-Host "No need to reboot: $_" -ForegroundColor Green
}
}