Convert VBS Script to List WMI Class Names to Powershell - 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.

Related

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.

Better way to query remote server registries?

I've built this small block of code to query and store the values of a group of servers, which seems to work fine, however I'd like to know if there is a "pure PowerShell" way to do this.
$eServers = Get-ExchangeServer
$Servers = $eServers | ?{$_.Name -like "Delimit_server_group"}
foreach ($server in $Servers)
{
[string]$Key1 = "\\$server\HKLM\SYSTEM\CurrentControlSet\Control\"
[string]$rKeys += (REG QUERY "$key1" /s)
}
You can use the RegistryKey class to open a remote registry:
$RemoteHKLM = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$server)
$RemoteKey = $RemoteHKLM.OpenSubKey('SYSTEM\CurrentControlSet\Control')
# Following will return all subkey names
$RemoteKey.GetSubKeyNames()
You'll have to implement recursive traversal yourself if you need functionality equivalent to reg query /s
Matthias' answer is probably your best option, but there are other approaches you could take as well. If you have PSRemoting enabled on your systems, you could for instance invoke remote commands like this:
$key = 'HKLM:\SYSTEM\CurrentControlSet\Control'
Invoke-Command -Computer $Servers -ScriptBlock {
Get-ChildItem $args[0] | Select-Object -Expand Name
} -ArgumentList $key

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

powershell output multiple variables derived from processlist

I have the below bit of code, it is designed to simply ask for a Windows ProcessName, and then return a list of all instances of that process that are running.
$processName = Read-Host 'Please Enter the Process Name, as shown in Task Manager (not inc *32)'
if (!$processName)
{
"No Process Given"
}
else
{
"You Entered "+$processName
$filter = "name like '%"+$processName+"'"
$result = Get-WmiObject win32_process -Filter $filter | select CommandLine
$counter=1
foreach($process in $result )
{
write-host "$counter) $process"
$counter++
}
}
It works fine up until the point where it outputs the list.
If I do
echo $process
then I get what I am after e.g.
"C:\folder\AppName.exe"
"C:\folder\AppName.exe instance1"
If however I try to concatenate the $counter in front of it I get:
1) #{CommandLine="C:\folder\AppName.exe" }
2) #{CommandLine="C:\folder\AppName.exe instance1" }
I've tried write-host, write-output, echo, various combinations of "", +, but I can't get rid of the #{CommandLine= xxx } when I try to combine it with another variable
Is there a way to get what I am after? e.g.:
1) "C:\folder\AppName.exe"
2) "C:\folder\AppName.exe instance1"
try write-host "$counter) $($process.commandline)"
OR modify your selection :
$result = Get-WmiObject win32_process -Filter $filter | select -expandproperty CommandLine
explanation : without expandproperty you get a psobject with expandproperty you have a string

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.