Why are the errors slipping through TRY/CATCH Loop in Powershell - powershell

I got a simple loop which gets all the serial Numbers in the Server List;
Start
foreach ($computer in $computers)
{
try
{
Get-WmiObject -computer $computer -Class Win32_OperatingSystem|Select Serial*
}
catch
{
Write-Host "Invalid Server"
}
}
END
But, the output looks all ugly with following errors as well as the correct outputs for few servers.
Get-WmiObject : The RPC server is unavailable
Get-WmiObject : Access Denied etc (Isnt it the purpose of Try/Catch loop to eliminate those?)
Strangely soemtimes the output says "Invalid Server" too , so what exactly is the difference between errors and what are the limitations of Try/Catch loops?
What am i doing wrong here? Please if any questions.

To make the above code throw an exception, you can add -ErrorAction Stop to your Get-WmiObject line, like this:
Get-WmiObject -computer $computer -Class Win32_OperatingSystem -ErrorAction Stop | Select Serial*
See this article by Keith Hill: distinction between "terminating" and "non-terminating" errors.

#Graimer the line $ErrorActionPreference = "Stop" did the trick and the output was clean without errors.

Related

Get-WmiObject not showing desired results while filtering for PNPDeviceID

I have been trying to get a script working that identifies whether the graphics card currently installed in a Windows 10 system is an Nvidia card or not via the hardwareID. But for whatever reason I can't get it to grab only the PNPDeviceID line that I want. I have setup 2 different test scripts in an attempt to get this working:
$VideoCardID = Get-WmiObject -ClassName Win32_VideoController | Where-Object { $_.PNPDeviceID -like "PCI\VEN_10DE*"}
Write-Host $VideoCardID
Write-Host "Press any key to exit..."
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
exit
Which outputs: \\DESKTOP-UPTIVUP\root\cimv2:Win32_VideoController.DeviceID="VideoController1" Press any key to exit...
And the other one:
$VideoCardID = Get-WmiObject -Class CIM_VideoController PNPDeviceID | Where-Object { $_."PCI\VEN_10DE*"}
Write-Host $VideoCardID
Write-Host "Press any key to exit..."
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
exit
Which just outputs Press any key to exit...
The desired result would be that it only detects the hardwareID (aka the PNPDeviceID) of the grafics card an then through and if/else statement figures out if its an Nvidia model or not.
I have been trying for some days now to figure out what's going wrong, but at this point I have given up, hence I am here to ask for advice.
This might be quite simple to solve for people with alot of experince with Powershell, although I'm still a script kiddie at best.
For your first snippet, you just need to use dot notation to reference the PNPDeviceID property of the object $VideoCardID:
Write-Host $VideoCardID.PNPDeviceID
The reason why you see the _PATH property is because Write-Host is stringifying your object, something similar to what would happen if we call the .ToString() method on this object:
PS /> $VideoCardID.ToString()
\\computername\root\cimv2:Win32_VideoController.DeviceID="VideoController1"
On your second snippet, your Where-Object statement is incorrect:
Where-Object { $_."PCI\VEN_10DE*"}
Should be the same as your first example:
Where-Object { $_.PNPDeviceID -like "PCI\VEN_10DE*"}
As for how you can approach the code with an if / else statement:
if((Get-CimInstance -Class CIM_VideoController).PNPDeviceID -like "PCI\VEN_10DE*") {
"It's NVIDIA!"
}
else {
"It's not NVIDIA!"
}
As aside, it is recommended to use the CIM cmdlets instead of WMI.

Get-WmiObject not functioning properly in foreach loop

I am currently trying to write a script that takes a list of Computers joined to our domain, iterate through them one at a time to check if they exist in an Access DB that I created, run WMI queries on them collecting their system info, and add that data to the DB if they aren't already in it. I am successfully able to do so on most of the computers (around half), but some of them say RPC server not found.
I know that some of these errors are due to computers being offline (the firewall is disabled and WMI querying is enabled). The problem is that some of the computers are online, and when I run the Get-WmiObject command on them in the script I get that RPC server error, but when I run the command separately outside of the script I am able to successfully query the information. I have posted the function that is causing the weird behavior and was hoping someone with more programming knowledge would find what noob mistake I am making.
The second problem is that after the first iteration I get the error below saying blank CompName field? The first two iterations work as expected then it just throws a bunch of these errors with the "Computer already exists after".
function Update-Systems {
$PSCredential = Get-Credential
$Comp = (Get-ADComputer -Filter * | select -ExpandProperty Name)
foreach ($Computer in $Comp) {
$RecordSet.MoveFirst()
$RecordSet.Find("CompName = '$Computer'")
$RecordCheck = $RecordSet.Fields.Item("CompName").Value
if (!$RecordCheck) {
"Collecting Data for $Record"
$SystemProp = Get-WmiObject -Class Win32_ComputerSystem -Credential $PSCredential -ComputerName: $Computer -ErrorAction SilentlyContinue
$RecordSet.Addnew()
$RecordSet.Fields.Item("DateRan") = Get-Date
$RecordSet.Fields.Item("Domain") = $SystemProp.Domain
$RecordSet.Fields.Item("CompName") = $SystemProp.Name
$RecordSet.Fields.Item("Model") = $SystemProp.Model
$RecordSet.Fields.Item("Manufacturer") = $SystemProp.Manufacturer
$RecordSet.Update()
} else {
"Computer already exists"
}
}
}
Most likely Get-WmiObject fails to query information from a remote computer. Since you instructed the cmdlet to just carry on in case of an error (-ErrorAction SilentlyContinue) the variable $SystemProp ends up empty when an error occurs, because of which $SystemProp.Name evaluates to $null as well.
You could work around the issue by assigning $Computer rather than $SystemProp.Name to the recordset, at least as a fallback like this:
$RecordSet.Fields.Item("CompName") = if (-not $SystemProp) {
$Computer
} else {
$SystemProp.Name
}
However, a better approach would be to do proper error handling:
$ErrorActionPreference = 'Stop'
try {
$SystemProp = Get-WmiObject -Class Win32_ComputerSystem -Credential $PSCredential -ComputerName $Computer
$RecordSet.AddNew()
$RecordSet.Fields.Item("DateRan") = Get-Date
$RecordSet.Fields.Item("Domain") = $SystemProp.Domain
$RecordSet.Fields.Item("CompName") = $SystemProp.Name
$RecordSet.Fields.Item("Model") = $SystemProp.Model
$RecordSet.Fields.Item("Manufacturer") = $SystemProp.Manufacturer
} catch {
Write-Error $_ -ErrorAction Continue
}
You could also retry a couple times before giving up.

What's the fastest way to get online computers

I'm writing a function which returns all Online Computers in our network, so I can do stuff like this:
Get-OnlineComputers | % { get-process -computername $_ }
Now I basically got my function ready, but it's taking way too long.
I want to only return Computers which have WinRM active, but I also want to provide the option to get every computer even those which haven't got WinRM set up (switch parameter).
This is my function. first it creates a pssession to the domaincontroller, to get all computers in our LAN. then foreach computer, it will test if they have WinRM active or if they accept ping. if so, it gets returned.
$session = New-PSSession Domaincontroller
$computers = Invoke-Command -Session $session { Get-ADComputer -filter * } | select -ExpandProperty Name
$computers | % {
if ($IncludeNoWinRM.IsPresent)
{
$ErrorActionPreference = "SilentlyContinue"
$ping = Test-NetConnection $_
if ($ping.PingSucceeded -eq 'True')
{
$_
}
}
else
{
$ErrorActionPreference = "SilentlyContinue"
$WinRM = Test-WSMan $_
if ($WinRM)
{
$_
}
}
}
Is this the best way I can go to check my online computers? Does anyone have a faster and better idea?
Thanks!
Very Quick Solution is using the -Quiet Parameter of the Test-Connection cmdlet:
so for example:
$ping = Test-Connection "Computer" -Quiet -Count 1
if ($ping)
{
"Online"
}
else
{
"Offline"
}
if it's not enough fast for you, you can use the Send Method of the System.Net.NetworkInformation.Ping
here's a sample function:
Function Test-Ping
{
Param($computer = "127.0.0.1")
$ping = new-object System.Net.NetworkInformation.Ping
Try
{
[void]$ping.send($computer,1)
$Online = $true
}
Catch
{
$Online = $False
}
Return $Online
}
Regarding execute it on multiple computers, I suggest using RunSpaces, as it's the fastest Multithreading you can get with PowerShell,
For more information see:
Runspaces vs Jobs
Basic Runspaces implemenation
Boe Prox (master of runspaces) has written a function which is available from the Powershell Gallery. I've linked the script below.
He uses many of the answers already given to achieve the simultaneous examination of 100s of computers by name. The script gets WMI network information if test-connection succeeds. It should be fairly easy to adapt to get any other information you want, or just return the result of the test-connection.
The script actually uses runspace pools rather than straight runspaces to limit the amount of simultaneous threads that your loop can spawn.
Boe also wrote the PoSH-RSJob module already referenced. This script will achieve what you want in native PoSH without having to install his module.
https://gallery.technet.microsoft.com/scriptcenter/Speedy-Network-Information-5b1406fb

Can I create a zombie process using powershell? (I know this sounds bad, but I'm need to test another script)

I know this is a pretty bad idea; but I have a script that checks for zombie processes, and I want to test it. Currently it looks at two separate processes across different machines. If their parent process ID doesn't exist, it echos the Hostname and PID. I think it is working correctly; but I always like to confirm my scripts with tests. Is there any way i could confirm that it is working correctly? I would want to create a zombie process; but I don't know if that would be the best way (also I don't know how I could easily create one in a controlled manner). Here is the script I wrote:
$m = "System1 System2 System3"
$zombies
foreach($a in $m.split(" "))
{
$pros = Get-WmiObject -Class Win32_Process -Namespace root/cimv2 -ComputerName $a
$pids = #()
foreach($pro in $pros)
{
$pids += $pro.ProcessId
}
foreach($pro in $($pros | Where-Object {$_.Name -eq "Proc1.exe" -or $_.Name -eq "Proc2.exe"}))
{
if($pids -notcontains $pro.ParentProcessId)
{
$zombies += "HOST: $(pro.MachineName) PID: $($pro.ProcessId)"
}
}
}
foreach($zombie in $zombies)
{
Write-Output $zombie
}
If you use the following command :
cmd /c Notepad.exe
You get the following result :
If you kill cmd.exe you will get :
The parent is still cmd.exe PID 8960 that just not exist. Ans as far as I understand another process can be created with PID 8960.

Try method in powershell

So I want to build a try method into my powershell script below. If I am denied access to a server, I want it to skip that server. Please help..
[code]$Computers = "server1", "server2"
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers | Select-Object `
#{n='Server';e={ $_.__SERVER }}, `
#{n='Physical Memory';e={ "$('{0:N2}' -f ($_.TotalPhysicalMemory / 1024))mb" }}, `
#{n='Virtual Memory';e={ "$('{0:N2}' -f ($_.TotalPageFileSpace / 1024))mb" }} | `
Export-CSV "output.csv"[/code]
Try/catch functionality is built-into PowerShell 2.0 e.g.:
PS> try {$i = 0; 1/$i } catch { Write-Debug $_.Exception.Message }; 'moving on'
Attempted to divide by zero.
moving on
Just wrap you script in a similar try/catch. Note you could totally ignore the error by leaving the catch block empty catch { } but I would recommend at least spitting out the error info if your $DebugPreference is set to 'Continue'.
You can simply suppress errors with the ErrorAction parameter:
Get-WmiObject Win32_LogicalMemoryConfiguration -Computer $Computers -ErrorAction SilentlyContinue | ...
You can use trap to replicate Try/Catch, see http://huddledmasses.org/trap-exception-in-powershell/ or http://weblogs.asp.net/adweigert/archive/2007/10/10/powershell-try-catch-finally-comes-to-life.aspx for examples.
Use a filter function? Like this tutorial explains.
He passes a list of computers to his pipeline - first it tries to ping each one, and then only passes the ones that respond to the next command (reboot). You could customize this for whatever actual functionality you wanted.