Monthly Archive

Categories

PowerShell v2

Just seen a question about PowerShell v2. PowerShell v2 was a huge step forward when it appeared in October 2009 as part of Windows 7 / Server 2008 R2

 

Windows 7 support finishes 14 January 2020

Windows Server 2008 R2 support finishes 14 January 2020

 

That’s less than 6 months.

 

PowerShell v2 isn’t mentioned in the Microsoft documentation which starts at PowerShell v3

 

If you’re still using PowerShell v2 its beyond time to move to a later version.

Test if a transcript is running

PowerShell has the ability to create a transcript of the commands you run at the console and the results displayed in the console. But how can you test if a transcript is running?

It used to be that you could only have a single transcript running but Windows PowerShell v5.1 and PowerShell v6.x and later allow multiple transcripts to be running in the same session.

Only way I can think of testing if transcript has been started is to use

Get-History | where CommandLine -like 'Start-Transcript*'

that doesn’t tell if its still running for which you need

Get-History | where CommandLine -like 'Stop-Transcript*'

PS> (Get-History | where CommandLine -like 'Start-Transcript*').Count - (Get-History | where CommandLine -like 'Stop-Transcript*').Count

should give a result of zero if no transcripts are running. A positive result indicates transcripts are running. A negative result indicates problems.

The test can be wrapped in a function

function test-runningtranscript {
$starts = (Get-History | Where-Object CommandLine -like 'Start-Transcript*').Count
$stops = (Get-History | Where-Object CommandLine -like 'Stop-Transcript*').Count

$trans = $starts - $stops

switch ($trans){
0 {$false}
{$_ -gt 0} {$true}
{$_ -lt 0} {Throw "Error!!! Can't have negative transcripts"}
}
}

You’ll get True return if there is a transcript running and False if there isn’t.

Identifying the host

Identifying the host in which your PowerShell code is running could be important. For instance you might not want to run some code that takes a long time to complete in VSCode – you may prefer to ensure it runs in the console as it consumes fewer resources.

 

You can identify the host – most of the time – using $host

 

For the PowerShell console – Windows PowerShell or PowerShell Core

PS> $host.Name
ConsoleHost

 

For ISE – Windows PowerShell

PS> $host.Name
Windows PowerShell ISE Host

 

For VScode – Windows PowerShell or PowerShell Core

PS> $host.Name
Visual Studio Code Host

 

For the new Windows Terminal – Windows PowerShell or PowerShell Core

PS> $host.Name
ConsoleHost

If you need to differentiate between the traditional PowerShell console and the new Windows terminal you’ll find that the Windows terminal adds an environmental variable

WT_SESSION

which takes a value of the form - 407c1756-556e-4df2-97db-c159a616b237

PowerShell Day UK 2019

The PowerShell Day UK 2019 one day conference is on Saturday 28 September 2019 - https://psday.uk/

 

I’ll be speaking and willing to answer any PowerShell questions that I can during the breaks.

 

If you have any books of mine that you want signing – bring them along and I’ll be happy to oblige.

Unblock and unzip

When you download a zip file from the Internet you have to unblock and unzip the file. I need to do this fairly often so wrote this simple function to perform both actions rather than doing it manually.

function unzipfile {
param (
[string]$path
)
Unblock-File -Path $path
Expand-Archive -Path $path -DestinationPath (Split-Path -Path $path -Parent)
}

 

The function takes a path and then uses Unblock-File and Expand-Archive. In theory you don’t need to unblock but if you don’t unblock the zip file you’ll have to unblock each and every file expanded from the archive if you want to edit it. Simpler to unblock once.

 

I’ve used Split-Path to get the folder containing the zip file to use as the destination. if you want to unzip to another folder change the function so destination becomes a second parameter

Variables in scriptblocks

I often see questions regarding the use of variables in scriptblocks. Usually a variable will be defined outside the scriptblock and then an attempt will be made to use it in the scriptblock:

PS> $path = 'C:\test\OldData01.txt'
PS> Start-Job -Name j1 -ScriptBlock {Get-FileHash -Path $path -Algorithm SHA256}

 

If you look at the output from the job you’ll see this error:

PS> Receive-Job -Name j1
Cannot bind argument to parameter 'Path' because it is null.
+ CategoryInfo : InvalidData: (:) [Get-FileHash], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetFileHashCommand
+ PSComputerName : localhost

 

The problem is that the job is running a different process, therefore a different scope and the variable $path isn’t defined in that scope.

 

The answer is to either use a param block in your scriptblock or the using scope modifier.

 

Starting with a param block

PS> Start-Job -Name j2 -ScriptBlock {param ($path) Get-FileHash -Path $path -Algorithm SHA256} -ArgumentList $path

 

The $path variable from the default scope is passed as an argument into the scriptblock which uses its internal $path variable.

 

PS> Start-Job -Name j3 -ScriptBlock {Get-FileHash -Path $using:path -Algorithm SHA256}

The using scope tells the script block to use the $path variable from the default scope.

 

In both cases the results are:

Algorithm : SHA256
Hash : 2B27E4F84D55C62D13C912C5298AA26602D41E90215D437D191E1D625AEB5244
Path : C:\test\OldData01.txt

Test local user doesn’t exist before creating

Saw a question asking how to Test local user doesn’t exist before creating. Windows 8 introduced the LocalAccounts module for Windows PowerShell. On Windows 10 1903 it runs in PowerShell v6/7.

 

There isn’t a Test-Localuser cmdlet but you can attempt to get the user before creation.

 

function new-user {
[CmdletBinding()]
param (
[string]$username
)
if (-not (Get-LocalUser -Name $username -ErrorAction SilentlyContinue)){
$pwd = Read-Host -Prompt "Password" -AsSecureString
New-LocalUser -Name $username -Password $pwd
}
else {
Write-Warning -Message "User:$username already exists"
}
}

 

The function takes a username as a parameter. Get-LocalUser is used to test if the user exists. If so the warning message is printed. If the user doesn’t exist you’re prompted for the password and New-LocalUser is used to create the account. You could add parameters for full name and description if required. It’s also possible to do something similar with Get-LocalGroup and New-LocalGroup

Missing verbs?

I saw a post that suggested that you can’t use Sort as a verb in your functions. You get a message that sort is an unapproved verb. Are there any other missing verbs?

I started with the object cmdlets as they are probably the most used cmdlets.

 

Running

Get-Command *-Object |
ForEach-Object {
$v = Get-Verb -Verb $_.Verb

$props = [ordered]@{
Cmdlet = $_.Name
Verb = $_.Verb
AliasPrefix = $v.AliasPrefix
Description = $v.Description
}
New-Object -TypeName PSobject -Property $props
}

 

against PowerShell v7 preview 2 I found that

 

Compare, Group, Measure, New and Select are approved verbs

 

Foreach, Sort, Tee and Where are unapproved verbs

 

Still trying to think through the logic in those choices

Sddl

An Sddl is a Security Descriptor Definition Language string - https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language - that provides a succinct way to provides the security descriptor of an object as a string. An example Sddl would be

O:BAG:S-1-5-21-437587817-63618879-1935034000-1001D:AI(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;0x1200a9;;;BU)

 

Now I’m sure that’s totally clear to everyone but just in case you can’t decode it PowerShell has a cmdlet – ConvertFrom-SddlString – that can help.

(Get-Acl -Path C:\test\erorfile.txt).Sddl | ConvertFrom-SddlString -Type FileSystemRights

 

If you want the output to be more readable try

((Get-Acl -Path C:\test\erorfile.txt).Sddl |
ConvertFrom-SddlString -Type FileSystemRights |
Select-Object -ExpandProperty DiscretionaryAcl) -split ':'

 

ConvertFrom-Sddl can work with permissions from file system, registry and Active Directory among others

Sort direction

By default Sort-Object uses an ascending sort direction.

 

Get-Command | Sort-Object -Property Source

will sort the commands based on the Source (module) in ascending Source order.

 

If you use multiple properties

Get-Command | Sort-Object -Property Source, Name, Version

 

Your output is sorted by Source, by Name within Source and by Version within Name.

All of these sorts are ascending sorts – A-Z or 1-9 etc

 

You can change the sort direction to Descending by using the Descending switch. If you have multiple properties involved in the sort they’ll be be sorted in a descending direction.

 

If you want some properties to be sorted ascending and some descending you need to explicitly specify the sort direction.

Get-Command | Sort-Object -Property @{Expression = 'Source'; Ascending = $true}, @{Expression = 'Name'; Ascending = $true}, @{Expression = 'Version'; Descending = $true}

 

A hash table is used to specify the property (Expression) and the sort direction.