Monthly Archive

Categories

Monthly Archives: July 2019

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.

Volume friendly name

When you use Get-Volume one of the properties displayed is the friendly name

PS> Get-Volume -DriveLetter C

DriveLetter FriendlyName FileSystemType
----------- ------------ --------------
C NTFS

 

In this case its blank because I haven’t set a volume label.

If you try to access the FriendlyName property

PS> (Get-Volume ).FriendlyName

 

You’ll get nothing returned.

 

The actual property is FileSystemLabel

PS> (Get-Volume ).FileSystemLabel

 

FriendlyName is an alias for FileSystemLabel created in the CDXML that is used to create the storage module and in the formating system for display.

 

Use

PS> Get-Volume -DriveLetter C | Format-List *

and

PS> Get-Volume -DriveLetter C | Get-Member

 

to view the properties on the object

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.

PowerShell v7 preview 2

PowerShell v7 preview 2 arrived a few days ago.

No big ticket items in this preview.

 

Some useful things:

Issue with Get-ChildItem Path with wildcards has been fixed

UseAbbreviationExpansion and TempDrive are moved from experimental features to official features

Foreach-Object is 2 times faster – that’s just the looping remember that any cmdlets executing in the scriptblock( s ) won’t necessarily be faster

 

The Get-ADUser issue hasn’t been fixed but the underlying problem has supposedly been fixed in .NET core so will hopefully bubble through in the next preview

PowerShell v6.2.2

PowerShell v6.2.2 has just been released.

 

One breaking change – the Enter-PSHostProcess is disabled when system is in lock down mode.

 

The jumplist is now created in STA to avoid potential CLR crash

 

Other changes are around the build process.

 

These changes shouldn’t have impact on your day to day scripts

 

PowerShell v6.1.5 has also been released with the same changes.

 

PowerShell v6.2.0 was released 28 March 2019 so you can expect the v6.1.x family to go out of support at the end of September 2019 assuming the lifecycle is followed. I recommend upgrading to v6.2.2 as soon as you can if you’re still using v6.1.x