The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,862 other subscribers

Archive for the ‘PowerShell’ Category

PowerShell: be careful using `-ReadCount` on `Get-Content`

Posted by jpluimers on 2019/08/14

I learned this the hard way: [WayBackDifferent result when using -ReadCount with Get-Content: because -ReadCount delivers data in chunks, the filter after [WayBack] Get-Content (Microsoft.PowerShell.Management) it will only filter on those chunks. If the filter isn’t prepared for that, it might only filter the last chunk.

So do not use for instance [WayBack] Select-String (Microsoft.PowerShell.Utility) on it, but perform your own [WayBack] ForEach-Object (Microsoft.PowerShell.Core) aliased as foreach like in [WayBack] Get all lines containing a string in a huge text file – as fast as possible?:

Get-Content myfile.txt -ReadCount 1000 |
  foreach { $_ -match "my_string" }

A more elaborate example is at [WayBack] How can I make this PowerShell script parse large files faster?.

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

Powershell: what kind of data type is [string[]] and when would you use it?

Posted by jpluimers on 2019/08/13

[WayBackIn Powershell, what kind of data type is [string[]] and when would you use it? (thanks cignul9 and arco444!): basically it forces an array of string.

It defines an array of strings. Consider the following ways of initialising an array:

[PS] > [string[]]$s1 = "foo","bar","one","two",3,4
[PS] > $s2 = "foo","bar","one","two",3,4

[PS] > $s1.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String[]                                 System.Array

[PS] > $s2.gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

By default, a powershell array is an array of objects that it will cast to a particular type if necessary. Look at how it’s decided what types the 5th element of each of these are:

[PS] > $s1[4].gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object


[PS] > $s2[4].gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


[PS] > $s1[4]
3
[PS] > $s2[4]
3

The use of [string[]] when creating $s1 has meant that a raw 3 passed to the array has been converted to a String type in contrast to an Int32 when stored in an Object array.

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

.NET and PowerShell: Getting proper version info from a PE file like EXE, DLL, assembly

Posted by jpluimers on 2019/08/01

I’ve learned the hard way that both .NET and PowerShell version information isn’t always accurate or usable for two reasons which I later found in various other blog and forum posts:

The easiest is to use these numbers to create a [WayBack] Version Class (System) instance using the [WayBack] Version Constructor (Int32, Int32, Int32, Int32) constructor. This has the added benefit that you directly compare versions with each other.

Sometimes it makes even sense to take the highest version from Product and File.

In PowerShell, this is the way to do that, assuming $imagePath points to a [WayBack] Portable Executable:

try {
  $VersionInfo = (Get-Item $imagePath).VersionInfo
  $FileVersion = [version]("{0}.{1}.{2}.{3}" -f $VersionInfo.FileMajorPart, $VersionInfo.FileMinorPart, $VersionInfo.FileBuildPart, $VersionInfo.FilePrivatePart)
  $ProductVersion = [version]("{0}.{1}.{2}.{3}" -f $VersionInfo.ProductMajorPart, $VersionInfo.ProductMinorPart, $VersionInfo.ProductBuildPart, $VersionInfo.ProductPrivatePart)
  $ActualVersion = $(if ($ProductVersion -gt $FileVersion) { $ProductVersion } else { $FileVersion })
}
catch {
  $ActualVersion = [version]("0.0.0.0")
}

Background information:

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

Ternary operator in PowerShell – Stack Overflow

Posted by jpluimers on 2019/07/31

I like this built-in construct by fbehrens most:

$result = If ($condition) {"true"} Else {"false"}

Everything else is incidental complexity and thus to be avoided.

For use in or as an expression, not just an assignment, wrap it in $(), thus:

write-host $(If ($condition) {"true"} Else {"false"})

There are even more elegant constructs, but those require setting up an alias before using them.

Source: [WayBackTernary operator in PowerShell – Stack Overflow

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

scripting – Run Multiple Powershell Scripts Sequentially – on a Folder – Combine Scripts into a Master Script – Stack Overflow

Posted by jpluimers on 2019/07/30

Cool tip by mjolinor to execute the scripts 1.ps1, 2.ps1 and 3.ps1 from a master.ps1 script in the same directory:

&"$PSScriptroot\1.ps1"
&"$PSScriptroot\2.ps1"
&"$PSScriptroot\3.ps1"

Source: [WayBackscripting – Run Multiple Powershell Scripts Sequentially – on a Folder – Combine Scripts into a Master Script – Stack Overflow.

It uses $PSScriptroot which got introduced in PowerShell 2 in modules and extended in PowerShell 3 to be available in all scripts. More information in [WayBack] about_Automatic_Variables | Microsoft Docs

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

powershell – How do I use join-path to combine more than two strings into a file path? – Stack Overflow

Posted by jpluimers on 2019/07/25

I love the solution with piped Join-Path constructs answered by David Keaveny in [WayBackpowershell – How do I use join-path to combine more than two strings into a file path? – Stack Overflow:

Since Join-Path can be piped its path value, you can pipe multiple Join-Path statements together:

Join-Path "C:" -ChildPath "Windows" | Join-Path -ChildPath "system32" | Join-Path -ChildPath "drivers"

Of course you could replace the built-in [WayBack] Join-Path by using using the .NET Framework [WayBack] Path.Combine Method (System.IO), but then you loose code completion.

If you do like that, here is how:

[System.IO.Path]::Combine("C:", "Windows", "system32", "drivers")

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

PowerShell script header to be as strict as possible and always throw exceptions

Posted by jpluimers on 2019/07/24

This is my default PowerShell script header from now on:

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$PSDefaultParameterValues['*:ErrorAction']='Stop'

This might sound like overkill, but it solves these problems:

The above helped me tremendously with for instance [WayBack] powershell – Why does the exception not get me in the catch block? – Stack Overflow:

I’m trying to interrogate some service information. Sometimes the installer of the application fails to correctly install, so the registry does not contain a service entry. I want to find out which installer steps did get executed correctly, even on systems that do not have proper logging in the installer.

If MyService does not exist, the script below does not go to the catch block even though the exception handling documentation suggests a bare catch should be enough:

try {
    $path = 'hklm:\SYSTEM\CurrentControlSet\services\MyService'
    $key = Get-Item $path
    $namevalues = $key | Select-Object -ExpandProperty Property |
        ForEach-Object {
        [PSCustomObject] @{
            Name = $_;
            Value = $key.GetValue($_)
        }
    }
    $namevalues | Format-Table
}
catch {
    $ProgramFilesX86 = [System.Environment]::GetFolderPath("ProgramFilesX86");
    $ProgramFiles = [System.Environment]::GetFolderPath("ProgramFiles");
    Write-Host $ProgramFilesX86
    Write-Host $ProgramFiles
}

Why is that and how should I force it to end up in the catch?

This is what PowerShell outputs:

Get-Item : Cannot find path 'HKLM:\SYSTEM\CurrentControlSet\services\MyService' because it does not exist.
At C:\Users\Developer\...\GetMyServiceInfo.ps1:17 char:12
+     $key = Get-Item $path
+            ~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (HKLM:\SYSTEM\Cu...vices\MyService:String) [Get-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand

For which I edited the answer to become this:

Force the error to be terminating:

$key = Get-Item $path -ErrorAction Stop

That way it will throw and catch will get it.

Explanation and links to the official Microsoft documentation:

What is missing there is the link to the $PSDefaultParameterValues documentation at [WayBack] about_Parameters_Default_Values | Microsoft Docs

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

DTW-DanWard/PowerShell-Beautifier: A whitespace reformatter and code cleaner for Windows PowerShell and PowerShell Core

Posted by jpluimers on 2019/07/23

Interesting tool: DTW-DanWard/PowerShell-Beautifier: A whitespace reformatter and code cleaner for Windows PowerShell and PowerShell Core

–jeroen

via: [WayBack] Is there a PowerShell code formatter / pretty printer? – Stack Overflow

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

PowerShell: Format-Table to show all columns/members

Posted by jpluimers on 2019/07/18

I’m not even sure if I’ve posted this before, but I always forget how to show all members (or columns) using Format-Table.

It’s dead easy: -Property *

Get-ChildItem | Format-Table -Property *

Later I found out this is equivalent with the shorter version where you omit the -Property part which I wrote about in [WayBackPowerShell: when Format-Table -AutoSize displays only 10 columns and uses the width of the console when redirecting to file.

So you can shorten the above to:

Get-ChildItem | Format-Table *

It has way more columns than this:

Get-ChildItem | Format-Table

The extra members in both marked with *:

  • PSPath
  • PSParentPath
  • PSChildName
  • PSDrive
  • PSProvider
  • PSIsContainer
  • BaseName
  • Mode *
  • Name *
  • FullName
  • Parent
  • Exists
  • Root
  • Extension
  • CreationTime
  • CreationTimeUtc
  • LastAccessTime
  • LastAccessTimeUtc
  • LastWriteTime *
  • LastWriteTimeUtc
  • Attributes

The odd thing: one property fails in the -Property * table:

  • Length

I tracked this down to how -Property * works: it takes the first entry in the list. If that is not a file, then it has no Length property: [WayBackpowershell – Measure-Object : The property “length” cannot be found in the input for any objects – Stack Overflow.

Note that for a GUI version, you can replace Format-Table with Out-GridView. See

-jeroen.

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »

PowerShell: the issues you bump into when using Get-Counter and Measure-Object aggregation

Posted by jpluimers on 2019/07/17

Single process data

You can get many process properties using , for instance the total running time in minutes [WayBack] Get-Process:

New-TimeSpan -Start (Get-Process "devenv").StartTime | Select-Object -ExpandProperty "TotalMinutes"

Actually, the it matches multiple process instances, then you need to aggregate, for instance using this:

Get-Process "cmd" | ForEach-Object { New-TimeSpan -Start $_.StartTime } | Measure-Object -Property "TotalMinutes" -Sum | Select-Object -ExpandProperty "Sum"

If you want to see which properties are available, then use either of these:

Get-Process | Select-Object * | Out-GridView
Get-Process | Get-Member

Getting aggregated process CPU usage times

Lets start getting the total CPU time of all processes by using Get-Process which – without arguments – returns data for all processes it has access to:

PS C:\Users\Developer> Get-Process | Measure-Object -Property CPU -Sum

You’d think this gives only the sum. Wrong:

Count : 80
Average :
Sum : 2410.5625
Maximum :
Minimum :
Property : CPU

Ensure you get data for all processes

And you need it with an Administrative UAC token in order to get all processes on the system. It doesn’t show many more processes, but a lot more CPU time since boot:

Count : 83
Average :
Sum : 6078.125
Maximum :
Minimum :
Property : CPU

Showing one aggregate

So how to get rid of [WayBack] Measure-Object showing all aggregates?

The only stable solution I found is to expand that into | Measure-Object -Property CPU -Sum | Select-Object -ExpandProperty Sum which I found in the answer by davidhigh at [WayBack] Listing processes by CPU usage percentage in powershell – Stack Overflow and got me this by using [WayBack] Select-Object.

Note you have to use -ExpandProperty as just -Propertywill not cut it as you get an empty result (I think this has to do with Sum being a nullable type: Sum Property System.Nullable[double]):

PS C:\Windows\system32> Get-Process | Measure-Object -Property CPU -Sum | Select-Object -Property Sum
PS C:\Windows\system32> Get-Process | Measure-Object -Property CPU -Sum | Select-Object -ExpandProperty Sum
6126.40625

CPU % is hard

You’d think a CPU % is an easy thing to measure as it has been available in the Task Manager for such a long time, but it’s harder to determine than for instance process memory. Let me try to explain that (please post comments when it is unclear).

For process memory, you can measure it at the current instant: if you freeze the CPU, you can count the number of bytes used by the process.

For relative CPU usage however, you need to measure how a process executes over time. During that time, CPU usage may (or will) vary a little bit, so you need to measure over a little time-delta. If CPU core frequencies vary over time, you need to adjust for that so it gets even more difficult. Two links with a bit more background information on this:

I think the above difference is the reason that Get-Process doesn’t return a CPU percentage.

Performance Counters using Get-Counter

Instead, you can use [WayBackGet-Counter to get processor CounterSampleslike this:

(Get-Counter '\Process(*)\% Processor Time').CounterSamples

It gets you the % Processor Time which is the percentage of CPU determined by a CPU [WayBackHardware performance counter.

Filtering results

If you want this for a specific process, you first need to find out which processes exist. You can do that by getting the unique InstanceName property values by using [WayBack] Sort-Object:

(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Select-Object -Property InstanceName | Sort-Object -Property InstanceName -Unique

(Here you do not need the -ExpandProperty on Select-Objectas the -Property suffices)

Special InstanceName values

There are always entries with InstanceName having values _total and idle:

(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "_total" }
(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "idle" }

You can even get both at the same time by using the -or operator with [WayBackWhere-Object:

(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {($_.InstanceName -eq "_total") -or ($_.InstanceName -eq "idle") }

Since the outputs are CPU core based, and sampled over time, they can be above 100%, heck even above the 100% * amount-of-CPU-cores as seen on a 2-core CPU system:

Path                                                InstanceName  CookedValue
----                                                ------------  -----------
\\w81entx64vs2015\process(idle)\% processor time    idle          186.168932138452
\\w81entx64vs2015\process(_total)\% processor time  _total        200.016208082634

You see that the CookedValue is what you’re after. If you just want the value for one instance, just use the -ExpandProperty trick shown above:

(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "_total" } | Select-Object -ExpandProperty CookedValue

Accommodating for multi-instance processes

(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "svchost" } | Select-Object -ExpandProperty CookedValue

This gets you a list like this:

PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "svchost" } | Select-Object -ExpandProperty CookedValue
0
0
...
0
0

For that, you need to aggregate by using Measure-Object like described in the Get-Process section:

PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "svchost" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
0

A more elaborate example of aggregating is at [Archive.isExportPerfomanceCountersHelpers.ps1.

Aggregated output usually works

This works for a single-instance process as well:

(Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "lsass" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum

Example output:

PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "lsass" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
0

The dreaded error

The above often works, but sometimes you get an error, often even earlier in the process.

The error is always like Get-Counter : The data in one of the performance counter samples is not valid. View the Status property for each PerformanceCounterSample object to make sure it contains valid data.:

PS C:\Windows\system32> (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.InstanceName -eq "lsass" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum
Get-Counter : The data in one of the performance counter samples is not valid. View the Status property for each PerformanceCounterSample object to make sure it contains valid data.
At line:1 char:2
+ (Get-Counter '\Process(*)\% Processor Time').CounterSamples | Where-Object {$_.I ...
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidResult: (:) [Get-Counter], Exception
    + FullyQualifiedErrorId : CounterApiError,Microsoft.PowerShell.Commands.GetCounterCommand

0

It took me a while to figure this out, but the root cause is that in-between Get-Counter retrieving the hardware process counters and processing the values, the CPU continues executing thereby changing some of the values. For instance, processes can quit (the CPU is not aware of processes, so binding CPU thread information from the hardware process counters to logical processes can fail in that step).

These links helped me tremendously as the -ErrorAction SilentlyContinue is not documented by Microsoft at Get-Counter:

Summary

Always use a construct like this to search for CPU usages on one or more processes named powershell:

(Get-Counter '\Process(*)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples | Where-Object {$_.InstanceName -eq "powershell" } | Measure-Object -Property CookedValue -Sum | Select-Object -ExpandProperty Sum

–jeroen

Posted in CommandLine, Development, PowerShell, PowerShell, Scripting, Software Development | Leave a Comment »