Powershell script very slow when running with very big array of data - powershell

ive been dabbling with powershell for a while now and ive been trying to modify some data in an array.
Problem is that my source array is very large and this script takes hours to run. Maybe someone can help my optimize my script.
With a small source array the script runs just fine btw.
$array_metric_hour = #()
$array_metric_hour =
foreach ($resource in $resources) {
Write-Progress -Id 0 "Step $resource"
foreach ($hour in $Time_Array) {
Write-Progress -Id 1 -ParentId 0 "Step $resource - Substep" ($hour.timestamp+":00")
[pscustomobject] #{
resourceID = $resource
resourceName = $array_bill.resources.($resource).name
time = $hour.timestamp+":00"
Poweredon = ((($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp}).poweredon | Measure-Object -Maximum).Maximum)
#Cpu_On = if (($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -eq "0,0"}).poweredon) {0} else {(($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -ne "0,0"}).provisionedCpu | Measure-Object -Maximum).Maximum}
Mem_GB_On = if (($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -eq "0,0"}).poweredon) {0} else {(($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -ne "0,0"}).provisionedMem_GB | Measure-Object -Maximum).Maximum}
hardware_Diskspace_GB = ((($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp}).hardware_Diskspace_GB | Measure-Object -Maximum).Maximum)
#used_Diskspace_GB = ((($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp}).used_Diskspace_GB | Measure-Object -Maximum).Maximum)
}
}
}
Some extra information that is required:
$Time_array has every full hour in a month, so 745 values in this case.
$array_combined exist of 98131 lines (5 minute interval with metrics during a month.
this array has the folowing items per interval.
resourceID
resourceName
timestamps
human_timestamp
hour
date
poweredOn
provisionedMem_GB
hardware_Diskspace_GB
used_Diskspace_GB

thanks for all the comments, next time ill try to supply all and correct information.
The suggestions of creating an extra filter was the winner for me. the scripts is 50 times faster in the current state and for now quick enough.
Added $filter1 and $filter2.
$array_metric_hour = #()
$array_metric_hour =
foreach ($resource in $resources) {
$filter1 = $Array_combined | Where-Object {$_.resourceID -eq $resource}
Write-Progress -Id 0 "Step $resource"
foreach ($hour in $Time_Array) {
$filter2 = $filter1 | Where-Object {$_.hour -eq $hour.timestamp}
Write-Progress -Id 1 -ParentId 0 "Step $resource - Substep" ($hour.timestamp+":00")
[pscustomobject] #{
resourceID = $resource
resourceName = $array_bill.resources.($resource).name
time = $hour.timestamp+":00"
Poweredon = if ($filter2 | where poweredOn -eq "0,0") {"0"} else {($filter2.poweredOn | Measure-Object -Maximum).Maximum}
Mem_GB_On = if ($filter2 | where poweredOn -eq "0,0") {0} else {(($filter2).provisionedMem_GB | Measure-Object -Average).Average}
hardware_Diskspace_GB = ((($filter2).hardware_Diskspace_GB | Measure-Object -Average).Average)
}
}
}

Since you're still not have provided much more of your code (e.g. where does "Array_combined" come from?), here are some important notes:
Don't use "Write-Progress" on every iteration! It has a very very huge impact on performace when using PS <=5.1, 6 and 7.
With the current "7.1" build I am using it works like a charm ("7.1.0-preview.7"). Have to look when they fixed it.
Avoid pipe'ing as much as you can when you want to have the best performance. Streaming data from one command to the other is compared to statements like "foreach {}" (NOT "Foreach-Object"!) really bad.
Here is an example for your template, even when there are some important steps missing:
# Progress bar definition
$progressActivity1 = 'Processing items'
$progressCounter1 = -1
$progressMax1 = #($resources).Count
$progressInterval1 = [math]::Ceiling($progressMax1 * 0.1) # each 10%
$progressId1 = 1
$progressParentId1 = 0
# *** use a list if your script adds objects several times.
# *** Note: "Arrays" are immutable and will be re-created each time you add something
$array_metric_hour = [System.Collections.Generic.List[psobject]]::new()
# *** good approach to add the result of a forEach-statement directly to variable. Performance is similar compared to adding objects to a list.
$array_metric_hour = foreach ($resource in $resources) {
# Progress bar counter & drawing (each 10%)
$progressCounter1++
If ($progressCounter1 % $progressInterval1 -eq 0) {
Write-Progress -Activity $progressActivity1 -PercentComplete($progressCounter1 / $progressMax1 * 100) -Id $progressId1 -ParentId $progressParentId1
}
# *** "Array_combined" is unknwon.... but according to the usage:
# !!! try to create a dictionary/hashtable of "Array_combined" with "resourceID" as key.
# !!! hash tables/dictionaries are much faster to access a particular item than arrays
# !!! access would be: $filter1 = $Array_combined[$resource]
$filter1 = $Array_combined | Where-Object { $_.resourceID -eq $resource }
# Progress bar definition
$progressActivity2 = 'Processing items'
$progressCounter2 = -1
$progressMax2 = #($Time_Array).Count
$progressInterval2 = [math]::Ceiling($progressMax2 * 0.1) # each 10%
$progressId2 = 2
$progressParentId2 = $progressId1
foreach ($hour in $Time_Array) {
# ??? don't know what $filter1 is about ...
# !!! replace that; use a hastable/dictionary
# !!! alternatively: use "foreach"-statement OR method ".where{}" which was introduced in PS 4.0
$filter2 = $filter1 | Where-Object { $_.hour -eq $hour.timestamp }
# Progress bar counter & drawing (each 10%)
$progressCounter2++
If ($progressCounter2 % $progressInterval2 -eq 0) {
Write-Progress -Activity $progressActivity2 -PercentComplete($progressCounter2 / $progressMax2 * 100) -Id $progressId2 -ParentId $progressParentId2
}
[pscustomobject] #{
resourceID = $resource
resourceName = $array_bill.resources.($resource).name
time = $hour.timestamp + ':00'
# ??? "Where-Object" could be replaced ... but don't know the background or data ....
# !!! replace "Measure-Object" with "[Linq.Enumerable]" methods if possible
Poweredon = if ($filter2 | Where-Object poweredOn -EQ '0,0') { '0' } else { ($filter2.poweredOn | Measure-Object -Maximum).Maximum }
# !!! same as above
Mem_GB_On = if ($filter2 | Where-Object poweredOn -EQ '0,0') { 0 } else { (($filter2).provisionedMem_GB | Measure-Object -Average).Average }
# !!! same as above
hardware_Diskspace_GB = ((($filter2).hardware_Diskspace_GB | Measure-Object -Average).Average)
}
}
}
# Progress completed
Write-Progress -Activity $progressActivity -Completed -Id $progressId1
Write-Progress -Activity $progressActivity -Completed -Id $progressId2

Related

Make a PSobject showing published modules and their download count by author

While I did write something that managed to work
99% of the time someone knows how to do it better than me
I am just looking to learn how to improve my code
$mymods = #()
Find-Module | Where-Object { $_.Author -eq 'NAME' } | %{$mymods += ($_).name}
$dlCount = #()
$mymods | %{((find-module $_).additionalmetadata).downloadCount} | %{$dlCount += $_}
[int]$max = $mymods.count
if ([int]$dlCount.count -gt [int]$mymods.count) {$max = $dlCount.Count}
$results = for( $i = 0; $i -lt $max; $i++)
{
Write-Verbose "$($mymods),$($dlCount)"
[PSCustomObject]#{
Modules = $mymods[$i]
Count = $dlCount[$i]
}
}
$results
You can simply do:
Find-Module | ? Author -match "someone" |
Select Name,Author,#{N="DownloadCount";E={$_.AdditionalMetadata.downloadCount}}
or:
$Modules | Group Author,{$_.AdditionalMetadata.downloadCount}
or:
I suggest you to first save the results of Find-Module To a variable and use it each time instead of loading it every request, will perform faster
$Modules = Find-Module

PowerShell Invoke Command, Script not returning some values from remote PC's

I'm new to scripting so please excuse me if my script is messy. This script pretty much does what I want it to do but for 2 fields it doesn't return the values.
If I run the commands without Invoke I get all the values I want but when I run this with the Invoke command on remote computers the OsHotFixes and CsProcessors return weird values of "Microsoft.PowerShell.Commands.HotFix" for each hotfix and "Microsoft.PowerShell.Commands.Processor" for the CsProcessors value. All other properties gave me the values I am looking for. I'm not sure why those 2 aren't returning correct values. If someone could point me in the right direction that would be awesome.
$c = Get-Content "myfilepath"
$e = "myfilepath"
$ScriptBlock = {
$ComputerInfo = Get-ComputerInfo -Property WindowsVersion, OsBuildNumber, OsHotFixes, CsModel, BiosSMBIOSBIOSVersion, WindowsProductName, CsProcessor, OsInstallDate, OsArchitecture, CsProcessors
$GPU = Get-WmiObject win32_VideoController | Select-Object "Name", "DeviceID", "DriverVersion"
$RAM = Get-CimInstance -ClassName CIM_PhysicalMemory | Select-Object "Manufacturer", "PartNumber", #{'Name'='Capacity (GB)'; 'Expression'={[math]::Truncate($_.capacity / 1GB)}}, "Speed"
$Storage = Get-WmiObject Win32_LogicalDisk | Where caption -eq "C:" | Foreach-object {write " $($_.caption) $('{0:N2}' -f ($_.Size/1gb)) GB total, $('{0:N2}' -f ($_.FreeSpace/1gb)) GB Free"}
$MyArray = #($ComputerInfo, $GPU, $RAM, $Storage)
$Properties =
#(
'WindowsVersion'
'OsBuildNumber'
'OsHotFixes'
'CsModel'
'BiosSMBIOSBIOSVersion'
'WindowsProductName'
'OsInstallDate'
'OsArchitecture'
'CsProcessors'
'Name'
'DeviceID'
'DriverVersion'
'Manufacturer'
'PartNumber'
'Capacity'
'Speed'
'Disk'
)
$MyArray | ForEach-Object {
:Inner ForEach( $Property in $Properties )
{
If($_.$Property)
{
[PSCustomObject][Ordered]#{
hostname = $env:COMPUTERNAME
WindowsVersion = $_.WindowsVersion
Build = $_.OsBuildNumber
Patches = $_.OsHotFixes
Motherboard = $_.CsModel
BiosVersion = $_.BiosSMBIOSBIOSVersion
WindowsProductName = $_.WindowsProductName
OsInstallDate = $_.OsInstallDate
OsArchitecture = $_.OsArchitecture
Processor = $_.CsProcessors
GPUName = $_.Name
DeviceID = $_.DeviceID
DriverVersion = $_.DriverVersion
RamManufacturer = $_.Manufacturer
PartNumber = $_.PartNumber
Capacity = $_.Capacity
Speed = $_.Speed
Disk = $Storage
}
Break Inner
}
}
}
}
Invoke-Command -ComputerName $c -ScriptBlock $ScriptBlock | Sort hostname | Export-Csv -append $e -NoTypeInformation
I've tried running just the lines from 4 - 8 locally and then Outputting the Array. This will show all correct values. However when this script runs with the PSCustomObject and Invoke command I don't get CsProcessors or OsHotFixes values.

Powershell - Where-Object in forEach Loop

I need some help for executing a little script and filtering results ...
I checkmy VM diskspace with the following script
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
$Report = "" | Select-Object -Property VM,Path,Capacity,FreeSpace,PercentageFreeSpace
$Report.VM = $VM.Name
$Report.Path = $_.Path
$Report.Capacity = $_.Capacity
$Report.FreeSpace = $_.FreeSpace
if ($_.Capacity) {$Report.PercentageFreeSpace = [math]::Round(100*($_.FreeSpace/$_.Capacity))}
$report
}
}
But I'd like to add a filter that my report only show me PercentageFreeSPace lesser than 20.
I try to add a where-object condition to my report with no success...
Can somebody help me please ? I m a beginner in PS...
Thanks in advance,
Best regards
For future questions you should format your script to be more readable.
I believe I have done it quite well below:
[EDIT] Seems that something gone wrong with displaying your question on my laptop. Just after my answer, the formatting is almost the same as provided below.
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
$Report = "" | Select-Object -Property VM,Path,Capacity,FreeSpace,PercentageFreeSpace $report
$Report.VM = $VM.Name
$Report.Path = $_.Path
$Report.Capacity = $_.Capacity
$Report.FreeSpace = $_.FreeSpace
if ($_.Capacity) {
$Report.PercentageFreeSpace = [math]::Round(100*($_.FreeSpace/$_.Capacity))
}
$report
}
}
To be honest I do not understand why you are using pipe at line 4.
Regarding your question, you should puy Where-Object clause before you will go to second for each loop.
First off, try and avoid multiple nested pipes to foreach-object, use foreach instead. That is, don't do
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
but
foreach($vm in Get-VM) {
foreach($disk in $vm.Guest.disks) {
This makes it easy to see what objects are handled later, and there isn't need to save current object on the pipeline into a temp variable (which you do by $VM = $_).
Also, objects can be initialized via a hash table instead of using select-object. Like so,
$pctLimit = 20 # Variable sets percentage limit instead of a magic number
foreach($vm in Get-VM) {
foreach($disk in $vm.Guest.Disks) {
# Report object is created via hash table
$Report = new-object PSObject -Property #{
VM = $VM.Name
Path = $disk.Path
Capacity = $disk.Capacity
FreeSpace = $disk.FreeSpace
ZeroDisk = ($disk.Capacity -gt 0) # NB: calculated property
}
if (-not $Report.ZeroDisk) { # Process only non-zero disks
$Report.PercentageFreeSpace = [math]::Round(100*($Report.FreeSpace/$Report.Capacity))
if($Report.PercentageFreeSpace -lt $pctLimit) {
$report
} else {
# Disk had more than $pctLimit free space. Now what?
}
} else {
# Disk had zero capacity. Now what?
}
}
}
FIrst of all thanks for your help
I manage to filter with the following
Get-VM | ForEach-Object {
$VM = $_
$_.Guest.Disks | ForEach-Object {
$Report = "" | Select-Object -Property VM,Path,Capacity,FreeSpace,PercentageFreeSpace
$Report.VM = $VM.Name
$Report.Path = $_.Path
$Report.Capacity = $_.Capacity
$Report.FreeSpace = $_.FreeSpace
if ($_.Capacity) {$Report.PercentageFreeSpace = [math]::Round(100*($_.FreeSpace/$_.Capacity))}
if( $_.Capacity -and $Report.PercentageFreeSpace -lt 30 ) {
$Report
}
}
}
`
Thanks,
Best regards

How to get a template list from several clusters?

I'm trying to migrate from a 6.0 vCenter to a 6.5 vCenter, and want to migrate all templates. How can I select all clusters at once to retrieve the full template list?
I have a lot of templates in my 6.0 vCenter and need to export a list to migrate them all at once in my new 6.5 vCenter, using Powercli. The only way I found is by using a foreach loop, in which I must provide a cluster name.
I tried using "get-datacenter" instead of "get-cluster" but the result is even worse.
$toto = foreach ($vmhost in Get-Cluster 'my_cluster'|Get-VMHost) {
Get-Template -Location $vmhost |
select name, #{n='VMHOST';e={$vmhost.name}},
#{n='VMTX';e={$_.extensiondata.config.files.VmPathName}}
}
$toto | Export-Csv C:\scripts\Templates.csv
The code works but doesn't show me all templates in the vCenter.
How can I make it work so that I can have all templates in all clusters at once, without using a loop for each?
The function extracts all templates from a single datacenter target by scanning all available clusters and hosts within; dumping the complete result into an $array_list variable.
Function Render_Template_List {
Clear-Host
$array_list = [System.Collections.ArrayList]#()
if (!($global:templates_bool)) {
$host.ui.RawUI.WindowTitle = "Retrieving available templates for use with '$global:datacenter' datacenter...Please Wait!"
if ($global:datacenter -eq 'none'){
$ESX = get-datacenter | get-VMhost | %{$_.Extensiondata.MoRef}
} else {
$ESX = get-datacenter -name "$global:datacenter" | get-VMhost | %{$_.Extensiondata.MoRef}
}
If ($global:Template -eq 'none') {
$Query = Get-Template | where {$ESX -contains $_.Extensiondata.Runtime.Host} | Sort-Object
} else {
$Query = Get-Template -name "$global:Template" | where {$ESX -contains $_.Extensiondata.Runtime.Host} | Sort-Object
}
$global:templates_bool = $true
$global:templates_query = $query
}
$seperator = "{,}"
$query = 0
$arr = 1
foreach ($template in $global:templates_query ) {
if ($arr -eq 1) {
foreach ($array in $global:templates_array) {
If (!($array -like "*#*")) {
$query = $query + 1
$val_template = $array.split($seperator)[2]
$val_template = $val_template.replace("`"","")
if ("$val_template" -eq "$template") {
$val_datacenter = $array.split($seperator)[1]
$val_datacenter = $val_datacenter.replace("`"","")
if ("$val_datacenter" -eq "$global:datacenter") {
$array_list.Add("$val_template") | Out-Null
}
}
}
}
}
if ($query -eq 0) {
$array_list.Add("$template") | Out-Null
$arr = 0
}
}
return $array_list
}

Powershell if within foreach

I'm trying to retrieve some information about my hard drives using PowerShell (very new to PS btw).
When calculating the occupancy of the drives some drives are not mounted and will return a size of 0. So I need to introduce a condition where a drive with size 0 must not calculate occupancy.
$fmt = "{0,-5} | {1,-18} | {2,12:N2} | {3,21:N2} | {4,17:p} | {5,-15}"
$docc = { if ( $_.size -ne "0" ) { (($_.size-$_.freespace)/$_.size)} else {"0"} }
Get-WmiObject Win32_logicaldisk | foreach -begin {
$fmt -f "Unit","File System","Capacity(GO)", `
"Available Space(GO)","Occupancy","Observation" } {
$fmt -f $_.deviceID, $_.FileSystem, ($_.size/1GB), `
($_.freespace/1GB), $docc, ""}
The preceding code will only return the if as a string not interpret it (even without using a variable).
You need to execute the scriptblock contained in $docc - I use the call (&) operator below. The scriptblock doesn't have access to $_ either, so I pass it as a parameter.
$fmt = "{0,-5} | {1,-18} | {2,12:N2} | {3,21:N2} | {4,17:p} | {5,-15}"
$docc = { param($item); if ( $item.size -gt 0 ) {
(($item.size-$item.freespace)/$item.size)} else {"0"} }
Get-WmiObject Win32_logicaldisk | foreach -begin {
$fmt -f "Unit","File System","Capacity(GO)", `
"Available Space(GO)","Occupancy","Observation" } {
$fmt -f $_.deviceID, $_.FileSystem, ($_.size/1GB), `
($_.freespace/1GB), (& $docc $_), ""}
Hope this helps.