WMI optimization - powershell

I have the following script.
$script:WMIClassNames = `
"CIM_Processor",`
"CIM_PhysicalMemory",`
"Win32_ComputerSystem",`
"CIM_BIOSElement",`
"Win32_OperatingSystem",`
"Win32_NetworkAdapterConfiguration",`
"Win32_Volume",`
"Win32_QuickFixEngineering",`
"CIM_Process",`
"WIN32_Service",`
"WIN32_NTLogEvent"
$script:WMIClassInfo = Get-WmiObject -ComputerName $script:strSysName -Namespace "Root/CIMV2" -List | Select Name | Where-Object {$script:WMIClassNames -contains $_.Name}
When i measure the time of the script, it is taking almost 4 to 10 min to execute this script on remote machines. Can this script be optimized so that the execution time can be reduced?
Thanks for the help.

Related

Stop Powershell Script if disk size is less than X

I wonder if you could help me out guys.
I need to stop a powershell script if the disk size of partition E is less than 10GB, and to continue if it´s more than 10GB.
So far i managed to get my disk size listed with this.
Get-WmiObject -Class win32_logicaldisk | Format-Table DeviceId,#{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}
And i get this result:
DeviceId
Freespace
A
0
C
77.9
D
0
E
34.05
So, i want to stop the powershell script if E unit has less than 10GB.
How can i do it?
Thanks in advance
If you want to put the Freespace of E in a variable you can do this :
$VarSpace = $(Get-WmiObject -Class win32_logicaldisk | Where-Object -Property Name -eq C:).FreeSpace/1GB
then you can do a simple if for check :
if ($VarSpace -le 10){ <Something for stopping you script like exit> }
You can use the Get-Volume cmdlet for that or Get-CimInstancerather than the old Get-WmiObject:
$freeOnE = (Get-CimInstance -ClassName win32_logicaldisk | Where-Object {$_.DeviceID -eq 'E:'}).FreeSpace / 1GB
or
$freeOnE = (Get-Volume -DriveLetter E).SizeRemaining / 1GB
Then exit your PowerShell session if this value is below 10Gb
if ($freeOnE -lt 10) { exit }

PowerShell interrogate a remote system

I have a function that I wrote that interrogates my local system. Just gathering whatever information I can get in a useful format. So I was wondering about a function that could interrogate other systems in this kind of way. function sys-remote <$ip-or-hostname> With that it could then try and return as much information about that system as it can. It's just an idea really, and I guess a number of points would be useful:
• With an IP address, how can we resolve the hostname in the most PowerShell'ish way?
• Whether a hostname of IP address is provided, can we resolve as much information as possible. i.e. MAC address, hostname, IP (and possibly other IP addresses if these can be visible to us)?
• Can we recover shared drives on that system so can see a list of possible shares to connect to.
• What about system information, would that always require WinRM, or can WMI or CIM suffice for most of the things in the below?
• Maybe return also a comma-separate list of whatever ports are open on that remote system if possible?
• What if the remote system is Linux. How much of the above can we reasonably obtain from a Linux system that we interrogate remotely from our Windows system (I guess that WinRM and WMI are out, but maybe CIM is still possible?)?
In general, it would be really useful to return a dump of information like this from a diagnostic point of view as would give a ton of information about a system to work from. Anything like the above (or indeed any other useful things to check for that I've not thought of here) would be really appreciated.
function sys {
$System = get-wmiobject -class "Win32_ComputerSystem"
$Mem = [math]::Ceiling($System.TotalPhysicalMemory / 1024 / 1024 / 1024)
$wmi = gwmi -class Win32_OperatingSystem -computer "."
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
$s = "" ; if ($uptime.Days -ne 1) {$s = "s"}
$uptime_string = "$($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min $($uptime.seconds) sec"
$job_cpu = Start-Job -ScriptBlock { (Get-WmiObject -Class Win32_Processor).Name }
$job_cpu_cores = Start-Job -ScriptBlock { (Get-WmiObject -Class Win32_Processor).NumberOfCores }
$job_cpu_logical = Start-Job -ScriptBlock { (Get-WmiObject -Class Win32_Processor).NumberOfLogicalProcessors }
""
"Hostname: $($System.Name)"
"Domain: $($System.Domain)"
"PrimaryOwner: $($System.PrimaryOwnerName)"
"Make/Model: $($System.Manufacturer) ($($System.Model))" # "ComputerModel: $((Get-WmiObject -Class:Win32_ComputerSystem).Model)"
"SerialNumber: $((Get-WmiObject -Class:Win32_BIOS).SerialNumber)"
"PowerShell: $($PSVersionTable.PSVersion)"
"Windows Version: $($PSVersionTable.BuildVersion), Windows ReleaseId: $((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'ReleaseId').ReleaseId)"
"Display Card: $((Get-WmiObject -Class:Win32_VideoController).Name)"
"Display Driver: $((Get-WmiObject -Class:Win32_VideoController).DriverVersion), Description: $((Get-WmiObject -Class:Win32_VideoController).VideoModeDescription)"
"Last Boot Time: $([Management.ManagementDateTimeConverter]::ToDateTime((Get-WmiObject Win32_OperatingSystem | select 'LastBootUpTime').LastBootUpTime)), Uptime: $uptime_string"
$IPDefaultAddress = #(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].IPAddress[0]
$IPDefaultGateway = #(Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.DefaultIpGateway})[0].DefaultIPGateway[0]
"Default IP: $IPDefaultAddress / $IPDefaultGateway"
Get-Netipaddress | where AddressFamily -eq IPv4 | select IPAddress,InterfaceIndex,InterfaceAlias | sort InterfaceIndex
""
Wait-Job $job_cpu | Out-Null ; $job_cpu_out = Receive-Job -Job $job_cpu
Wait-Job $job_cpu_cores | Out-Null ; $job_cpu_cores_out = Receive-Job -Job $job_cpu_cores
Wait-Job $job_cpu_logical | Out-Null ; $job_cpu_logical_out = Receive-Job -Job $job_cpu_logical
"CPU: $job_cpu_out"
"CPU Cores: $job_cpu_cores_out, CPU Logical Cores: $job_cpu_logical_out"
# Get-PSDrive | sort -Descending Free | Format-Table
gwmi win32_logicaldisk | Format-Table DeviceId, VolumeName, #{n="Size(GB)";e={[math]::Round($_.Size/1GB,2)}},#{n="Free(GB)";e={[math]::Round($_.FreeSpace/1GB,2)}}
gwmi win32_winsat | select-object CPUScore,D3DScore,DiskScore,GraphicsScore,MemoryScore,WinSPRLevel | ft # removed ,WinSATAssessmentState
get-WmiObject -class Win32_Share | ft
}
No reason to do this sort of thing from scratch. There are many existing scripts for what you are doing. Via the Microsoft powershellgallery.com.
PowerShell Script For Desktop Inventory Basic script to collect
desktop inventory.
PowerShell Hardware Inventory Script Scenario:PowerShell hardware
Inventory Script. Have you ever wanted to have an inventory without the
hassle of going to each finding the information needed to fill the
information for your inventory? It is important to keep your inventory
up to date. Every time there is a change y
DownloadGet-Inventory.ps1
You can just take your script and use Invoke-Command (Runs commands on local and remote computers.) in a PowerShell remote session to get remote computer info.

PowerShell property expression increases execution time by 4-5 times

Scroll down for TL;DR
I need to get the following properties for every process as quickly as possible, ideally 5 seconds, maximum 10 seconds: ID, Name, Description, Path, Company, Username, Session ID, StartTime, Memory, CPU (percentage, not time)
To get this data, I put together the following snippet which (I think) is functionally perfect:
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
The issue is that its taking 18-22 seconds to execute, caused by this line (which adds about 16 seconds):
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
}
TotalSeconds : 19.061206
When I remove the slow property expression noted above and keep the WMI query, execution takes about 4.5 seconds:
Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
}
TotalSeconds : 4.5202906
I thought that by getting all of the required data in a single query and referring back to the $ProcessCPU array would be fast - but I appreciate I'm iterating through each of the 250 arrays stored in $Processes.
TL;DR:
Is there a more performant method of joining two objects on a common property rather than using iteration as I have above? I.E. $ProcessCPU.IDProcess on $Processes.Id?
I tried the following block to test $Output = $ProcessCPU + $Processes | Group-Object -Property Id, it executed in just 3 seconds, but the output wasn't acceptable:
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object #{Name='Id';Expression={[int]$_.IDProcess}}, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
$Output = $ProcessCPU + $Processes | Group-Object -Property Id
}
TotalSeconds : 2.9656969
Use CIM to build up a hashtable that maps process IDs (PIDs) to their CPU percentages first.
Then make the calculated property passed to Select-Object consult that hashtable for efficient lookups:
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
ForEach-Object -Begin { $htCpuPctg=#{} } `
-Process { $htCpuPctg[$_.IdProcess] = $_.PercentProcessorTime } #`
Get-Process -IncludeUserName |
Select-Object Id,
Name,
Description,
Path,
Company,
UserName,
SessionId,
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={ $htCpuPctg[[uint32] $_.Id] }}
Note:
Get-CimInstance rather than Get-WimObject is used, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell Core, where all future effort will go, doesn't even have them anymore. For more information, see this answer.
There is usually no need to use calculated properties such as #{Name='Id';Expression={[int]$_.Id}} to simply extract a property as-is - just use the property's name - Id - as a Select-Object -Property argument (but you've since clarified that you're using calculated properties because you want explicit control over the property's data type for sending data to an IoT Gateway via JSON).
Note that CIM reports PIDs as [uint32]-typed values, whereas Get-Process uses [int] values - hence the need to cast to [uint32] in the hashtable lookup.

PowerShell Script Not Working as Expected (foreach loop)

I'm using below PowerShell script to set server power plan to High Performance mode. The issue is, it's making changes only to the server where the I'm executing the script even after passing server names through a text file (Servers.txt). I've used foreach loop to iterate through the server list, but still no luck. Not sure where I'm missing the logic, can someone help with this. Thanks in advance.
$file = get-content J:\PowerShell\PowerPlan\Servers.txt
foreach ( $args in $file)
{
write-host "`r`n`r`n`r`nSERVER: " $args
Try
{
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
#Set power plan to High Performance
write-host "`r`n<<<<<Changin the power plan to High Performance mode>>>>>"
$HighPerf = powercfg -l | %{if($_.contains("High performance")) {$_.split()[3]}}
$CurrPlan = $(powercfg -getactivescheme).split()[3]
if ($CurrPlan -ne $HighPerf) {powercfg -setactive $HighPerf}
#Validate the change
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
}
Catch
{
Write-Warning -Message "Can't set power plan to high performance, have a look!!"
}
}
The problem is that although a foreach loop is used to iterate all the servers, the names are never used for actual power configuration. That is,
$HighPerf = powercfg -l | %{if($_.contains("High performance")) {$_.split()[3]}}
will always be executed on the local system. Thus, no power plan is changed on remote server.
As a work-around, maybe psexec or Powershell remoting would do, as powercfg doesn't seem to support remote system management.
The MS Scripting Guys have a WMI based solution too, as usual.
From the Gist of your Question,I think you may wanna try running the complete Set of commands in Invoke-Command Invoke-Command Documentation and pass the system name in -ComputerName
$file = get-content J:\PowerShell\PowerPlan\Servers.txt
foreach ( $args in $file)
{
invoke-command -computername $args -ScriptBlock {
write-host "`r`n`r`n`r`nSERVER: " $args
Try
{
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
#Set power plan to High Performance
write-host "`r`n<<<<<Changin the power plan to High Performance mode>>>>>"
$HighPerf = powercfg -l | %{if($_.contains("High performance")) {$_.split()[3]}}
$CurrPlan = $(powercfg -getactivescheme).split()[3]
if ($CurrPlan -ne $HighPerf) {powercfg -setactive $HighPerf}
#Validate the change
gwmi -NS root\cimv2\power -Class win32_PowerPlan -CN $args | select ElementName, IsActive | ft -a
}
Catch
{
Write-Warning -Message "Can't set power plan to high performance, have a look!!"
}
}
}

Duplicates in Workflow VM audit

I'm implementing a VM audit of several vcenters (around 5 of them) where the report is simply a csv of each VM along with a few properties. Although this script would run overnight, I found that it took around 5-6 hours to complete and wanted to increase its efficiency. I learned about workflows and figured it would be faster to audit each vcenter at the same time instead of one by one. It was slower than I expected finishing after about 4 hours. I noticed that there were many duplicates in the data and I can't figure out why that would be; maybe my ideas about how workflow works is flawed. I'm also looking for any tips on raising efficiency in my code. Thanks in advance.
The workflow:
workflow test {
param([string[]]$vcenters, [string]$session, [string]$username, [string]$password)
foreach -parallel($vcenter in $vcenters){
$main = InlineScript{
Add-PSSnapin VMware.VimAutomation.Core
Connect-VIServer -Server $Using:vcenter -User $Using:username -Password $Using:password
$vms = Get-View -ViewType VirtualMachine -Property Name, Summary.Config.GuestFullName, Runtime.Powerstate, Config.Hardware.MemoryMB, Config.Hardware.NumCPU
ForEach($machine in $vms){
$vm = Get-VM -Server $Using:vcenter -Name $machine.Name -ErrorAction SilentlyContinue
$object = New-Object -Type PSObject -Property ([ordered]#{
Name = $machine.Name
GuestOS = $machine.Summary.Config.GuestFullName
PowerState = $machine.Runtime.PowerState
MemoryGB = ($machine.Config.Hardware.MemoryMB / 1024)
CPU = $machine.Config.Hardware.NumCPU
VLAN=(Get-NetworkAdapter -VM $vm |Sort-Object NetworkName |Select -Unique -Expand NetworkName) -join '; '
})
$object| Export-Csv -Append “C:\TestReports\$($vcenter)_TestReport.csv” -NoTypeInformation
}
Disconnect-VIServer - $Using:vcenter -Confirm:$false
}
}
}
With the below changes, maybe it runs quickly enough that you no longer need parallelism or workflow. Not sure if those elements are a cause of the duplication problem. If not, you might need to share more details from your environment for help with that piece.
Get-VM is slow. You're calling it once for each VM, and I don't think you need it at all. Try adding this line after connecting to vCenter
$networks = Get-View -ViewType Network
Replacing your VLAN= line with
VLAN= $networks | ? {$_.VM.Value -contains $machine.MoRef.value} | select -exp Name
And dropping your $vm = Get-VM... line entirely.