Get-WmiObject not functioning properly in foreach loop - powershell

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.

Related

Get-Counter not retrieving valid details

I am trying to retrieve I/O details from the task manager using PowerShell. Below is the script that I am trying to use as of now and have had partial success with the same.
$gc = get-counter -ComputerName $($service.ServerName) "\Process($Tservicename)\IO Other Bytes/sec" -ErrorAction SilentlyContinue
$OtherBytes=$gc.CounterSamples|Select cookedvalue
Here $serviceName & ServerName is looped through. Below are the issues that I am facing.
I am unable to retrieve IO Other bytes details for all the service, I am running the script in admin mode so the access shouldn't be an issue.
Will the above script give the cumulative result in case there is more than process is executing, for eg for the chrome.exe there would be multiple services running, will it provide an cumulative value. If not how I extract details for each process of the chrome.
-- Updated Question--
We are using Get-Counter cmdlet to retrieve the IO read and write bytes details. This returns 0 for most of the process, is this due to being unable to access system process or due to the access issue.
-- Answer--
After researching a bit realized that value retrieved by the cmdlet is based on that particular instance, which is why we need to sampling of the data by using the SampleInterval. However my requirement was sufficed by using the RawValue
parameter since I was looking for the value post the server startup as a cumulative value.
List item
Two important things to understand here:
There's not a 1-to-1 relationship between service names and process names
Performance counter object instance names don't use process ID's (by default at least)
That means you need to do two levels of instance translation to make sense of the counters:
Translate Service to Process ID - we can use Win32_Service WMI class for this
Translate Process ID to Process counter instances - we can use the Process(*)\ID Process counter value for this
function Get-ServiceCounter
{
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]
$ServiceName,
[Parameter(Position = 1)]
[string]
$ValueName
[string]
$Computername = '.'
)
$ID = Get-CimInstance Win32_Service -Filter "Name = '${ServiceName}'" -ComputerName $ComputerName -ErrorAction SilentlyContinue |Select -Expand ProcessID
if(-not $ID){
Write-Error "Could not resolve process for service '${ServiceName}'"
return
}
$Instance = Get-Counter -ComputerName $ComputerName "\Process(*)\ID Process" |Where-Object CookedValue -eq $ID -ErrorAction SilentlyContinue
if(-not $Instance){
Write-Error "Could not performance counter instance for Process ID ${ID}"
return
}
Get-Counter ($InstanceName -replace '\\id process',"\${ValueName}") -ComputerName $ComputerName -ErrorAction SilentlyContinue
}

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

Need to check for the existence of an account if true skip if false create account

I am trying to create local user on all servers and I want to schedule this as a scheduled task so that it can run continually capturing all new servers that are created.
I want to be able to check for the existence of an account and if true, skip; if false, create account.
I have imported a module called getlocalAccount.psm1 which allows me to return all local accounts on the server and another function called Add-LocaluserAccount
which allows me to add local accounts these work with no problems
when I try and run the script I have created the script runs but does not add accounts
Import-Module "H:\powershell scripts\GetLocalAccount.psm1"
Function Add-LocalUserAccount{
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName=$env:computername,
[parameter(Mandatory=$true)]
[string]$UserName,
[parameter(Mandatory=$true)]
[string]$Password,
[switch]$PasswordNeverExpires,
[string]$Description
)
foreach ($comp in $ComputerName){
[ADSI]$server="WinNT://$comp"
$user=$server.Create("User",$UserName)
$user.SetPassword($Password)
if ($Description){
$user.Put("Description",$Description)
}
if ($PasswordNeverExpires){
$flag=$User.UserFlags.value -bor 0x10000
$user.put("userflags",$flag)
}
$user.SetInfo()
}
}
$usr = "icec"
$rand = New-Object System.Random
$computers = "ServerA.","ServerB","Serverc","ServerD","ServerE"
Foreach ($Comp in $Computers){
if (Test-Connection -CN $comp -Count 1 -BufferSize 16 -Quiet){
$admin = $usr + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122)
Get-OSCLocalAccount -ComputerName $comp | select-Object {$_.name -like "icec*"}
if ($_.name -eq $false) {
Add-LocalUserAccount -ComputerName $comp -username $admin -Password "password" -PasswordNeverExpires
}
Write-Output "$comp online $admin"
} Else {
Write-Output "$comp Offline"
}
}
Why bother checking? You can't create an account that already exists; you will receive an error. And with the ubiquitous -ErrorAction parameter, you can determine how that ought to be dealt with, such as having the script Continue. Going beyond that, you can use a try-catch block to gracefully handle those exceptions and provide better output/logging options.
Regarding your specific script, please provide the actual error you receive. If it returns no error but performs no action check the following:
Event Logs on the target computer
Results of -Verbose or -Debug output from the cmdlets you employ in your script
ProcMon or so to see what system calls, if any, happen.
On a sidenote, please do not tag your post with v2 and v3. If you need a v2 compatible answer, then tag it with v2. Piling on all the tags with the word "powershell" in them will not get the question answered faster or more effectively.
You can do a quick check for a local account like so:
Get-WmiObject Win32_UserAccount -Filter "LocalAccount='true' and Name='Administrator'"
If they already exist, you can either output an error (Write-Error "User $UserName Already exists"), write a warning (Write-Warning "User $UserName Already exists"), or simply silently skip the option.
Please don't use -ErrorAction SilentlyContinue. Ever. It will hide future bugs and frustrate you when you go looking for them.
This can very easily be done in one line:
Get-LocalUser 'username'
Therefore, to do it as an if statement:
if((Get-LocalUser 'username').Enabled) { # do something }
If you're not sure what the local users are, you can list all of them:
Get-LocalUser *
If the user is not in that list, then the user is not a local user and you probably need to look somewhere else (e.g. Local Groups / AD Users / AD Groups
There are similar commands for looking those up, but I will not outline them here

Convert VBS Script to List WMI Class Names to Powershell

Basically I'd like to use Powershell to dump a list of all available classes in the root\cimv2 namespace. I have a vbscript which accomplishes the task:
Set objWMIService = _
GetObject("winmgmts:{impersonationLevel=impersonate}root\cimv2")
Set colClasses = objWMIService.SubClassesOf
For Each objClass In colClasses
If Left(objClass.Path_.Class,6) = "Win32_" Then
WScript.Echo objClass.Path_.Class
End If
Next
I've been able to get powershell to retrieve the list, but I can't seem to figure out how to get it to Write-Host the names. This is where I'm at now:
$WMIService = Get-WmiObject -Namespace root\cimv2 -List
$aClasses = $WMIService.SubClassesOf
foreach ($Class in $aClasses) {
Write-Host $Class.Path_.Class
}
Powershell dumps a long list of nothing, so I know it's enumerated something. I've tried all sorts of $Class.x and haven't hit the magic one yet. Does anyone know?
Something similar to:
$WMIService = Get-WmiObject -Namespace root\cimv2 -List
$WMIService | where { $_.Name -like "Win32_*" } | foreach { $_.Name }
Will get you what you're looking for, I think.

Why are the errors slipping through TRY/CATCH Loop in 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.