Monthly Archive

Categories

Monthly Archives: June 2017

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

https://blogs.msdn.microsoft.com/powershell/2017/06/09/getting-started-with-powershell-core-on-windows-mac-and-linux/

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.