PowerShell - get-hotfix on multiple servers - error handling - powershell

I have a problem with my script to check multiple hotfixes on multiple servers. Sometimes I don't have a rpc connection to a server, in this case I want to log this information to the same output file. Can someone please help me? thanks
$computers = Get-Content -path C:\00-Scripts\printer\server.txt
$Patch = Get-Content -path C:\00-Scripts\printer\kb.txt
foreach ($computer in $computers) {
foreach ($patch1 in $patch) {
Try {
if (get-hotfix -id $patch1 -ComputerName $computer -ErrorAction stop) {
Add-content "$patch1 is Present in $computer" -path C:\00-Scripts\printer\Hotfix.txt
}
Else {
Add-content "$patch1 is not Present in $computer" -path C:\00-Scripts\printer\Hotfix.txt
}
}
catch {
Add-content "can not check $computer" -path C:\00-Scripts\printer\Hotfix.txt
}
}
}

In this case, you need to first check if the computer can be reached. If so, loop over the patches to report if they can be found or not. In you cannot reach the machine, write just one failure line in the log and proceed with the next computer.
$computers = Get-Content -path 'C:\00-Scripts\printer\server.txt'
$Patch = Get-Content -path 'C:\00-Scripts\printer\kb.txt'
$logFile = 'C:\00-Scripts\printer\Hotfix.txt'
foreach ($computer in $computers) {
if (Test-Connection -ComputerName $computer -Count 1 -Quiet) {
foreach ($patch1 in $patch) {
# use -ErrorAction SilentlyContinue here so $hotfix will either become $null or an object
$hotfix = Get-HotFix -Id $patch1 -ComputerName $computer -ErrorAction SilentlyContinue
if ($hotfix) {
"$patch1 is Present in $computer" | Add-Content -Path $logFile
}
else {
"$patch1 is not Present in $computer" | Add-Content -Path $logFile
}
}
}
else {
"Can not check $computer" | Add-Content -Path $logFile
}
}

With the above answer, script is not scanning the next computer and the results are not getting generated for the rest of computers
for example
Get-HotFix -ComputerName PC1,PC2 -ErrorAction SilentlyContinue
In above command if PC1 is not available or access denied. The command stops wihtout checking PC2 and generating output

Related

Powershell Error Handling not working in Invoke-command block

I want to print error message when service is not found or machine is offline
$csv = Import-Csv "C:\Users\user\Desktop\computers.csv" -delimiter ","
foreach ($cs in $csv){
$computers = $cs.DNSHostname
foreach ($computer in $computers){
Try{
Invoke-Command -ComputerName $computer -ScriptBlock {
$ProcessCheck = Get-Process -Name agentid-service -ErrorAction Stop
if ($null -eq $ProcessCheck) {
Write-output "agentid-service IS not running $env:computername"
}
else {
Write-output "agentid-service IS running $env:computername"
}
}
}
catch{
Write-Warning $Error[0]
}
}
}
Instead, when service name is not found, i'm getting error:
Cannot find a process with the name "agentid-service". Verify the process name and call the cmdlet again.
And if connection to computer is not possible then getting:
[vm.domain.local] Connecting to remote server vm.domain.local failed with the following error message : The WinRM client cannot process the request because the server name cannot be resolved.
And script continue execution (as it should).
How can i get "Catch" block to be executed ?
You can do this in one foreach loop.
Try
$computers = (Import-Csv "C:\Users\user\Desktop\computers.csv").DNSHostname
foreach ($computer in $computers) {
if (!(Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
Write-Warning "Computer $computer cannot be reached"
continue # skip this one and proceed with the next computer
}
try{
Invoke-Command -ComputerName $computer -ScriptBlock {
$ProcessCheck = Get-Process -Name 'agentid-service' -ErrorAction Stop
if (!$ProcessCheck.Responding) {
"agentid-service is NOT not running on computer '$env:computername'"
}
else {
"agentid-service is running on computer '$env:computername'"
}
}
}
catch{
Write-Warning $_.Exception.Message
}
}
Thanks to #Theo's answer, managed to fix it:
$csv = Import-Csv "C:\Users\user\Desktop\computers.csv" -delimiter ","
foreach ($cs in $csv){
$error.Clear()
$computers = $cs.DNSHostname
foreach ($computer in $computers){
Try{
Invoke-Command -ComputerName $computer -ScriptBlock {
$ProcessCheck = Get-Process -Name agentid-service -ErrorAction Stop
if ($null -ne $ProcessCheck) {
Write-output "agentid-service IS running $env:computername"
}
} -ErrorAction Stop
}
catch{
if ($null -eq $ProcessCheck){
Write-warning "agentid-service IS not running $env:computername"
}
Write-Warning $Error[0]
}
}
}

Check if process is running on multiple remote computers and copy a file if process is NOT running, keep list of those which succeeded

I need to copy a file to multiple computers, but can only do so if a particular app (process) is not running.
I know I can use Invoke-Command to run a script (scriptblock) on a list of machines.
But how can I check if process is running on the machine and then only copy file if it is not running.
So that at the end of running against a load of computers I can easily see those which succeeded e.g. process was not running and file was copied
Thanks
UPDATE:
I am assuming something like this will do the first bits of what I am asking, but how to visually show or log success or failure so I know which computers have been done - doesn't need to be anything fancy, even if simply a variable that holds computername of those where process wasn't running and file was copied okay
Invoke-Command -ComputerName PC1, PC2, PC3 -ScriptBlock {
If ((Get-process -Name notepad -ea SilentlyContinue) -eq $Null){
Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "C:\test\file.txt" -Force
}
}
$Procs = invoke-command -ComputerName PC1 { get-process | Select Name }
If($Procs -notmatch "Notepad"){ Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$PC1\c$\test\" -Force}
edited:
$computers = #("PC1","PC2","PC3")
Foreach($computer in $computers){
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){"$Computer - not running Notepad"; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){"$Computer - is running Notepad"}
}
Edit2(for clean output):
$computers = #("PC1","PC2","PC3")
$RNote = #()
$NNote = #()
$off = #()
Foreach($computer in $computers){
$TestC = Test-Connection -ComputerName $computer -Count 1
If(!($TestC)){$off += $computer} Else{
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){$NNote +=$computer; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){$RNote +=$computer}
}
}
$leng =[array]$RNote.count,$NNote.Count,$off.count
[int]$max = ($leng | measure -Maximum).Maximum
for($i=0; $i -lt $max;$i++){
[pscustomobject]#{
"Notepad On" = $(if ($RNote[$i]){$RNote[$i]})
"Notepad Off" = $(if ($NNote[$i]){$NNote[$i]})
"Offline " = $(if ($off[$i]){$off[$i]})
}
}
I think this is what you're looking for or at least close:
$Results = #()
$Results +=
Invoke-Command -ComputerName DellXPS137000, DellXPS8920 -ScriptBlock {
$GPArgs = #{Name = "Notepad++"
ErrorAction = "SilentlyContinue"}
If ( $Null -ne (get-process #GPArgs )) {
#Process your copy here
$Status = "Success"
}
Else {$Status = "Failed"}
$Machine =
(Get-CimInstance -ClassName 'Win32_OperatingSystem').CSName
Return ,"$Machine : $Status"
}
Value of $Results:
PS> $results
DELLXPS137000 : Success
DELLXPS8920 : Failed
HTH

How can I speed up this Powershell Loop

I am very new at Powershell, but I recently decided to try and recreate an old tool we sometimes use but as a Powershell script. The script takes in user input to get an IP Range then pings, gets hostname, checks if service exists and is running, and then exports it to a CSV file. I haven't done too much cleaning up of the code yet. It does what I want but when I was testing it on larger IP ranges it was taking several minutes ie. 1-254 took around 10 minutes. Any tips would be appreciated.
Add-Content -Path ".\$fullrange.csv" -Value '"Ping","IP","Hostname","Service State","Startup","Version"'
ForEach($IP in $range) {
$status = test-connection -computername ($IP) -count 1 -Quiet -ErrorAction SilentlyContinue
if ($status) {
$hostname = [System.Net.Dns]::GetHostbyAddress("$IP")."HostName"
$service = Get-WmiObject -Class Win32_Service -ComputerName $IP -ErrorAction SilentlyContinue | Where-Object Name -like "SysaidAgent"
if ($service) {
$xml = [xml](Get-Content "\\$IP\C$\Program Files\SysAid\Configuration\AgentConfigurationFile.xml")
$ver = $xml.SelectSingleNode("//AgentVersion")."#text"
}
else{
$service = "Not Found"
$ver = "N/A"
}
Add-Content -Path .\$fullrange.csv -Value "$status,$IP,$hostname,$service.state,$service.startmode,$ver"
}
else{
Add-Content -Path .\$fullrange.csv -Value "$status,$IP"
}
}

Powershell: Trying to locate file in multiple drives from list of servers

I'm trying (and failing) to:
Connect to the server by iterating through a list.
Confirm location where file exists (1 of 3 locations).
Replace a string in that file.
I've tried to do this multiple ways. There are two that I have which do part of what I want.
Can someone please help me understand if there's something I'm doing inefficiently or how to put all this together?
This one can loop through the servers and find the file
$ErrorActionPreference = 'SilentlyContinue'
$nope=$null
$servers= Get-Content C:\Servers.txt
foreach ($server in $servers)
{
If (Test-Connection -ComputerName $server -Quiet)
{Invoke-Command -ComputerName $server -ScriptBlock {$file=(Get-Childitem -Path C:\DiskSpace.ps1, D:\DiskSpace.ps1, Y:\DiskSpace.ps1); Write-Host "Found $file on $env:computername."}}
Else {
Write-Host ">> Could not connect to $server."; $nope += $server}
}
Write-Host $nope
...and this one can at least find a local file
$valid=#('')
$paths = #("C:\Users\user_name\Desktop\DiskSpace.ps1","C:\DiskSpace.ps1","D:\DiskSpace.ps1","Y:\DiskSpace.ps1")
Foreach ($path in $paths)
{
if (Test-Path $path)
{$valid += $path}
}
write-host $valid
Here's how I intend to to replace the string:
$ErrorActionPreference = 'SilentlyContinue'
$find=(Get-Childitem -Path C:\, D:\, Y:\ -include DiskSpace.ps1 -Recurse)
Write-Host $find
$ErrorActionPreference = 'Stop'
try {
(Get-Content $find).replace('bad_email#domain.com', 'good_email#domain.com') | Set-Content $find
}
catch {
}
Get-Content $find
You had all the pieces already. Simply loop over your Get-Content command for each file in the Invoke-Command.
$ErrorActionPreference = 'SilentlyContinue'
$servers = Get-Content C:\Servers.txt
$files = #('C:\DiskSpace.ps1', 'D:\DiskSpace.ps1', 'Y:\DiskSpace.ps1')
$report = foreach ($server in $servers) {
if (Test-Connection -ComputerName $server -Quiet) {
$response = Invoke-Command -ComputerName $server -ScriptBlock {
Get-Childitem -Path $using:files | ForEach-Object {
(Get-Content $_).replace('bad_email#domain.com', 'good_email#domain.com') | Set-Content $_
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "$($_.fullname) updated."
}
}
}
if ($response -eq $null) {
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "No files found"
}
} else {
$response
}
} else {
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "Unreachable"
}
}
}
$report

List installed applications on remote servers

I have pieced together the following script which lists all installed applications on a local system and writes it into a logfile, but I am unaware how to get this same output when using PSRemoteRegistry, the actual input list I need this against will be all remote targets.
Does anyone have experience with fitting this same code into the cmdlets available through PSRemoteRegistry? Specifically I need it to enumerate the displayname of every installed app found in the key HKLM:\Software\Microsoft\CurrentVersion\Uninstall
The piece I am needing help with getting into the PSRemoteRegistry cmdlets is:
Get-ChildItem 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object {Log (Get-ItemProperty $_.pspath).DisplayName}
and this is the entire script:
clear
#$ErrorActionPreference = "silentlycontinue"
$Logfile = "C:\temp\installed_apps.log"
Function Log
{
param([string]$logstring)
Add-Content $Logfile -Value $logstring
}
$target_list = Get-Content -Path c:\temp\installed_apps_targets.txt
foreach ($system in $target_list){
if (test-connection $system -quiet)
{
Log "---------------Begin $system---------------"
Get-ChildItem 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object {Log (Get-ItemProperty $_.pspath).DisplayName}
Log "---------------End $system---------------"
}
else
{
Log "**************$system was unreachable**************"
}
}
You can adapt something like this:
$Computer = "ComputerName"
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer)
$RegKey= $Reg.OpenSubKey('Software\Microsoft\Windows\CurrentVersion\Uninstall')
$Keys = $RegKey.GetSubKeyNames()
$Keys | ForEach-Object {
$Subkey = $RegKey.OpenSubKey("$_")
Write-Host $Subkey.GetValue('DisplayName')
}
Have you met Invoke-Command?
$Logfile = "C:\temp\installed_apps.log"
Function Log() {
param([string]$logstring)
Add-Content $Logfile -Value $logstring
}
$scriptbock = {Get-ChildItem 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object {Log (Get-ItemProperty $_.pspath).DisplayName}}
Get-Content -Path c:\temp\installed_apps_targets.txt | % {
if (test-connection $_ -quiet) {
Log "---------------Begin $system---------------"
Log $(Invoke-Command -ComputerName $_ -ScriptBlock $scriptblock)
Log "---------------End $system---------------"
}
else
{
Log "**************$system was unreachable**************"
}
}