Learning PowerShell
A recent post on powershell.org - https://powershell.org/2017/06/22/taking-powershell-to-the-next-level/ - gave this path for learning PowerShell and becoming more proficient
Books:
Learn Powershell In A Month of Lunches
Learn Powershell Toolmaking in a month of Lunches
Windows Powershell In Action 3rd Edition
Online:
Advanced Tools And Scripting with Powershell 3.0 Jump Start
Writing Powershell Powershell DSC Resources And Configuration
Demo Code
To that list I’d add
Books: PowerShell in Depth 2nd edition – read it before PowerShell in Action
In parallel with the books and online courses find an area that you can work in – Active directory, Exchange, Windows admin, Hyper-V, SharePoint, SQL Server or whatever and start solving practical problems. You’ll learn more fro doing that than all the books you’ll ever read
Finding a CIM class
One of the problems you might find is finding a CIM class. You know its name but you don’t know which namespace its in.
The old WMI cmdlets allow you to search the namespaces recursively
PS> Get-WmiObject -Class Win32_Process -Namespace root -Recurse -List NameSpace: ROOT\CIMV2 Name Methods Properties ---- ------- ---------- Win32_Process {Create, Terminat... {Caption, CommandLine, CreationClassName, CreationDate...}
But the CIM cmdlets don’t have this functionality. I’ve been meaning to do something about this for ages but finally got motivated by something I read while proof reading PowerShell in Action – yes its getting closer, much closer.
What I ended up with is these 2 functions
function get-namespace { [cmdletBinding()] param ([string]$namespace = 'root') Get-CimInstance -Namespace $namespace -ClassName '__NAMESPACE' | foreach { "$namespace\" + $_.Name get-namespace $("$namespace\" + $_.Name) } } function find-cimclass { [cmdletBinding()] param ( [string]$namespace = 'root', [string]$classname ) $class = $null ## test namespace for class $class = Get-CimClass -Namespace $namespace -ClassName $classname -ErrorAction SilentlyContinue if (-not $class) { $namespaces = get-namespace -namespace $namespace foreach ($name in $namespaces){ $class = $null $class = Get-CimClass -Namespace $name -ClassName $classname -ErrorAction SilentlyContinue if ($class){break} } } $class }
Find-Cimclass takes a namespace and class name as parameters. It tries to find the class in the given namespace. If it can’t find it then get-namespace is called to generate a list of namespaces to search. The function iterates over the collection of namespaces testing each one for the class. When it finds the class it returns the class information.
Get-namespace searches for all instances of the __Namespace class in the given namespace. it then recursively call itself to test each of those namespaces. That way you get the whole tree.
If you’re searching for a given class I recommend that you start at the root class to ensure that you test everywhere.
Joining and Testing folder paths
Last time I showed how to split folder paths to just leave the path – no filenames or drive information. What about the opposite task – joining and testing folder paths.
Here’s an example
$basepath = 'C:\Scripts' $pathsTotest = 'Containers','HyperV', 'NanoServer', 'NoSuchFolder' $pathsToTest | foreach { $path = Join-Path -Path $basepath -ChildPath $psitem Test-Path -Path $path }
When you run this the paths to test are joined to the base path and then tested.
The output is
True True True False
which isn’t the most informative you’ll ever see.
The output can easily be made more friendly
$basepath = 'C:\Scripts' $pathsTotest = 'Containers','HyperV', 'NanoServer', 'NoSuchFolder' $pathsToTest | foreach { $path = Join-Path -Path $basepath -ChildPath $psitem $props = @{ Path = $path Found = Test-Path -Path $path } New-Object -TypeName psobject -Property $props }
Create an object for the output using the full path you’re testing and the result
The output looks like this
Path Found ---- ----- C:\Scripts\Containers True C:\Scripts\HyperV True C:\Scripts\NanoServer True C:\Scripts\NoSuchFolder False
You could save the output as a CSV for later work if required
Just the folders
Lets say you have a bunch of files in nested folders but you just want the folders not the file or drive
Our files look like this
C:\Scripts\HyperV\Setup C:\Scripts\HyperV\attachdisks.ps1 C:\Scripts\HyperV\get-mountedvhdDrive.ps1 C:\Scripts\HyperV\invoke-CIMshutdown.ps1 C:\Scripts\HyperV\set-loopbackswitch.ps1 C:\Scripts\HyperV\Set-NestedVirtualisation.ps1 C:\Scripts\HyperV\set-realswitch.ps1 C:\Scripts\HyperV\Start-AllWindowsVMs.ps1 C:\Scripts\HyperV\Stop-AllVMs.ps1 C:\Scripts\HyperV\Stop-Lab.ps1 C:\Scripts\HyperV\test-HotfixIpresence.ps1 C:\Scripts\HyperV\Setup\Copy-Updates.ps1 C:\Scripts\HyperV\Setup\Get-LicenseStatus.ps1 C:\Scripts\HyperV\Setup\Install-RollUp.ps1 C:\Scripts\HyperV\Setup\New-VirtualMachine.ps1 C:\Scripts\HyperV\Setup\SCsetup.ps1 C:\Scripts\HyperV\Setup\Set-VMconfig1.ps1 C:\Scripts\HyperV\Setup\Set-VMconfig2.ps1 C:\Scripts\HyperV\Setup\setup (2).ps1 C:\Scripts\HyperV\Setup\setup.ps1 C:\Scripts\HyperV\Setup\sysprep.txt
You can strip out the file names using Split-Path and the –Parent parameter
PS> Get-ChildItem .\HyperV\ -Recurse | foreach {Split-Path -Path $_.Fullname -Parent } C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup C:\Scripts\HyperV\Setup
Split-Path also has a –NoQualifier parameter that strips of the drive – unfortunately its in a different parameter set to –Parent BUT you can use a pipeline
PS> Get-ChildItem .\HyperV\ -Recurse | foreach {Split-Path -Path $_.Fullname -Parent | Split-Path -NoQualifier } \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup \Scripts\HyperV\Setup
If you just want the folders and don’t care about the files try
PS> Get-ChildItem -Path .\ -Recurse -Directory | where FullName -Like "*HyperV*" | foreach {Split-Path -Path $_.Fullname -Parent | Split-Path -NoQualifier } \Scripts \Scripts\HyperV
Location, location
Just recently I’ve found my self repeatedly working through a location, location pattern.
cd C:\test\ .\hello.ps1 cd C:\Scripts\
The pattern consists of changing to another folder. Running some code and then changing back to the original folder – assuming you can remember it.
I then remembered the location cmdlets
PS> Get-Command *-Location | ft -a CommandType Name Version Source ----------- ---- ------- ------ Cmdlet Get-Location 3.1.0.0 Microsoft.PowerShell.Management Cmdlet Pop-Location 3.1.0.0 Microsoft.PowerShell.Management Cmdlet Push-Location 3.1.0.0 Microsoft.PowerShell.Management Cmdlet Set-Location 3.1.0.0 Microsoft.PowerShell.Management
The 2 useful ones in this working pattern are Push-Location and Pop-Location
Push-Location adds the current location to the location stack and moves you to a new location if you supply a path
Pop-Location pulls the topmost location from the location stack and moves you to that location
The pattern then becomes
PS> Get-Location Path ---- C:\Scripts PS> Push-Location -Path C:\test\ PS> Get-Location Path ---- C:\test PS> .\hello.ps1 Hello world! PS> Pop-Location PS> Get-Location Path ---- C:\Scripts
Saves all that remembering stuff
$using
A comment on yesterday’s post about passing parameters into a script block asked why I hadn’t mention $using
$using allows you to access a local variable in a scriptblock
BUT you need to be careful
PS> $proc = "power*" Invoke-Command -ScriptBlock { Get-Process -Name $using:proc } A Using variable cannot be retrieved. A Using variable can be used only with Invoke-Command, Start-Job, or InlineScript in the script workflow. When it is used with Invoke-Command, the Using variable is valid only if the script block is invoked on a remote computer. At line:4 char:4 + Get-Process -Name $using:proc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : UsingWithoutInvokeCommand
What about the call operator?
PS> $proc = "power*" & { Get-Process -Name $using:proc } A Using variable cannot be retrieved. A Using variable can be used only with Invoke-Command, Start-Job, or InlineScript in the script workflow. When it is used with Invoke-Command, the Using variable is valid only if the script block is invoked on a remote computer. At line:4 char:4 + Get-Process -Name $using:proc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : UsingWithoutInvokeCommand
And in a job?
PS> $proc = "power*" Start-Job -ScriptBlock { Get-Process -Name $using:proc } Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 3 Job3 BackgroundJob Running True localhost PS> Receive-Job -Id 3 Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName ------- ------ ----- ----- ------ -- -- ----------- 510 26 42192 58004 0.55 9172 9 powershell 634 30 73112 90028 1.53 14680 9 powershell 955 68 161040 159956 12.23 4012 9 powershell_ise
You can’t use $using with the call operator and if you use it with Invoke-Command you have to be accessing a remote computer.
$using with InlineScript is for accessing variables defined elsewhere in the workflow
PowerShell on Linux
An introduction to PowerShell v6 on Windows, mac and Linux is available here
Well worth a read if you haven’t looked at PowerShell v6 yet
Passing parameters to a script block
Passing parameters to a scriptblock seems to be an issue at the moment.
Consider a simple scriptblock
Invoke-Command -ScriptBlock {Get-Process}
How can you modify that to parameterise the processes that are returned.
Its a two step process. Add a parameter block to your script block and secondly pass the correct values to the scriptblock
Invoke-Command -ScriptBlock {
param ($proc )
Get-Process -Name $proc
} -ArgumentList "power*"
If you want to pass an array of values to the scriptblock you have 2 options. First abandon the named parameter
$p = ("power*", "win*")
Invoke-Command -ScriptBlock {
Get-Process -Name $args
} -ArgumentList $p
if you use $args then all arguments are available through the array
Scriptblocks unravel the array of arguments so if you want named parameters then you need to force the scriptblock to accept an array by using the unary comma operator
$p = ("power*", "win*")
Invoke-Command -ScriptBlock {
param ($proc )
Get-Process -Name $proc
} -ArgumentList (,$p)
Generating passwords
Generating new passwords can be a painful business. There are many ways of accomplishing password generation – depending on your needs. One suggestion for generating passwords is to use a GUID as the basis of the password
PS> New-Guid Guid ---- 269f328d-b80d-446a-a14c-6197ff1bcc40
You could then remove the hyphens and extract part of the guid
PS> (New-Guid).Tostring() -replace '-' c5023096aee24b3ba1d9988ff1c774e4
You need to decide the length of the password. Guids are 32 characters so make sure you start your extraction in a position that gives room for the length you need
$length = 8 ((New-Guid).Tostring() -replace '-').Substring((Get-Random -Minimum 0 -Maximum (31-$length)), $length)
Your results will look like these examples
fbe8e66e 980d4032 0341d71f 6f6478fd fbfea1ce 34694bc6 62666733 b1419ac0 3cf8aa7d
The drawback is that you only have numbers and lower case characters
If you use the Membership class from System.Web you can do this
PS> Add-Type -AssemblyName System.Web PS> [System.Web.Security.Membership]::GeneratePassword(8,2) 1L*q381)
The GeneratePassword method takes 2 arguments – the first is the length of the password and the second is the number of non-alphanumeric characters
If you’re running PowerShell v5 you can utilise the using keyword instead of Add-Type
PS> using assembly System.Web PS> [System.Web.Security.Membership]::GeneratePassword(8,1) f>jg84XR
Nano server changes
Nano server is the small, really small, footprint install version of Windows Server that was introduced with Server 2016.
It has a limited number of roles available to to install – much like the original version of Server core.
Recent announcements - https://blogs.technet.microsoft.com/hybridcloud/2017/06/15/delivering-continuous-innovation-with-windows-server/
https://docs.microsoft.com/en-us/windows-server/get-started/nano-in-semi-annual-channel
indicates that Nano server is going to become even smaller (by more than 50%) and dedicated to delivering Containers. The infrastructure related roles will be removed. Nano Server will ONLY be available as a container base OS image
In addition, starting this Autumn, Nano server and Server Core will getting 2 feature updates per year.