PowerShell for Windows Admins


June 29, 2017  2:56 PM

Joining and Testing folder paths

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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

June 29, 2017  1:50 PM

Just the folders

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

Lets say you have a bunch of files in nested folders but you want just 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


June 28, 2017  12:42 PM

Location, location

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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


June 27, 2017  2:01 PM

$using

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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


June 27, 2017  10:52 AM

PowerShell on Linux

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

An introduction to PowerShell v6 on Windows, mac and Linux is available here

Getting Started with PowerShell Core on Windows, Mac, and Linux

Well worth a read if you haven’t looked at PowerShell v6 yet


June 26, 2017  1:43 PM

Passing parameters to a script block

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Powershell

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)


June 26, 2017  10:10 AM

Generating passwords

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
"Computer security", Password, Powershell

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 are 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


June 24, 2017  11:07 AM

Nano server changes

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
containers, Windows Server 2016

Nano server is the small, really small, footprint install version of Windows Server that was introduced with Server 2016. Nano server changes are coming.

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.


June 14, 2017  1:43 PM

Deal of the Day – 15 June 2017

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
Books, Hyper-V, Powershell

My book is Manning’s Deal of the Day  – 15 June 2017:

Half off Learn Hyper-V in a Month of Lunches. Use code dotd061517au at http://bit.ly/2rZXI9x

Sign up for DoD notifications at https://www.manning.com/dotd


June 12, 2017  7:57 AM

Find the logged on user

Richard Siddaway Richard Siddaway Profile: Richard Siddaway
CIM, Powershell, WMI

One method of finding the logged on users is to use CIM

$ComputerName = $env:COMPUTERNAME
Get-CimInstance -ClassName Win32_Process -ComputerName $ComputerName -Filter "Name = 'explorer.exe'" | 
foreach {
    
    $lguser = Invoke-CimMethod -InputObject $psitem -MethodName GetOwner
    
    $Properties = @{
       ComputerName = $ComputerName
       User = $lguser.User
       Domain = $lguser.Domain
       Time = $User.CreationDate 
    }
          
    New-Object -TypeName PSObject -Property $Properties
 }

Get the Win32_Process instances for explorer.exe and foreach of them use the GetOwner method  to get the owners names and domain. Create an object and ouput


Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: