Monthly Archive

Categories

PowerShell

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.

Using aliases in scripts

There’s been a long debate on the PowerShell github site regarding ternary operators – think of it as a short cut for if-else. Twice in that debate the point has been made that aliases are perfectly acceptable in scripts. Wrong. Using aliases in scripts should never be encouraged.

 

Aliases are short cuts to cmdlet and parameter names. In some instances a parameter name has changed but the old name is available as an alias so existing code doesn’t break.

 

Using aliases at the command line is perfectly acceptable as they cut down typing – debateable with PSUseAbbreviationExpansion in PowerShell v6/7 – but I can see their use in that situation. I tend to use full cmdlet names – more from habit due to doing so many demos, articles and books – but understand that many people use aliases.

 

Aliases in scripts are a bad idea because they cause confusion. They’re more difficult to understand, especially for new comers, and make code harder to maintain especially for someone other than the original writer.

 

If you use VScode or ISEsteroids they will explicitly flag aliases to be expanded. PSScriptAnalyzer (also used in VScode) will also flag aliases that should be expanded.

 

Given tab completion and the intellisense built into VScode and ISE using aliases in your code is just bad practice. There is no need to do so and very good reasons why you shouldn’t.