Thursday, November 19, 2015

Working with registry values in PowerShell 5

Working with the registry in PowerShell has always kind of sucked. Now, in PowerShell 5, it’s, well, different, a little.

It’s mostly the same, but there is one new command, which does nothing new, but does it in a new way. I guess that’s progress.

To get a registry key, you still use Get-Item.

001
$Key = Get-Item HKLM:\SOFTWARE\7-zip

To get a registry value, you still use Get-ItemProperty to get all of the values in a key, and pipe them to Select-Object to get just the value you want, in this case, a value named “Path”.

001
$Value = Get-ItemProperty HKLM:\SOFTWARE\7-zip | Select Path

That results in a custom object with a property named Path. So if what you really need to work with the Data in the Value (stupid registry terminology), you would previously use this.

001
$Data = Get-ItemProperty HKLM:\SOFTWARE\7-zip | Select -ExpandProperty Path

Or this.

001
$Data = (Get-ItemProperty HKLM:\SOFTWARE\7-zip).Path

In PowerShell 5, you can now also do it this way.

001
$Data = Get-ItemPropertyValue HKLM:\SOFTWARE\7-zip -Name Path

Wednesday, November 18, 2015

Native zip commands in PowerShell 5

PowerShell 5 includes native commands for working with zip files. Woohoo!

They haven’t given us full functionality yet, but it’s definitely better than nothing.

We can create a zip file from a selection of files or folders using Compress-Archive.

001
Get-Item D:\test | Compress-Archive -DestinationPath D:\test2\TestArchive.zip

We can overwrite it with a new .zip file by using the –Force switch.

We can add or modify files in an existing .zip file using the –Update switch.

001
Get-Item D:\TCPSU | Compress-Archive -DestinationPath D:\test2\TestArchive.zip -Update

We unzip the .zip file using Expand-Archive.

001
Expand-Archive -Path D:\test2\TestArchive.zip -DestinationPath D:\test4

Use the –Force switch to overwrite files.

You can set the –CompressionLevel to Fastest instead of the default Optimal. Theoretically this will compress faster, but result in larger files. In my testing, there was no significant difference in either stat. Your mileage may vary depending on your specific files.

You can also set the –CompressionLevel to NoCompression. This can be useful if most of your files are already compressed; for example, .jpg, .msi, or .xlsx files. This allows you to combine them into a single package without wasting time trying to compress things that aren’t compressible.

What’s missing in this version

You can’t look at a .zip file and know what’s in it without unzipping it.

You can’t extract less than everything in the zip file.

You can’t use any archive type other than .zip.

The drive to get this command set into this version of PowerShell was the ability to use it with DSC. As a result, the features they focused on were the ones that are used within DSC. They did not have time to finish some of the features that would be useful in other scenarios. For those use cases, you will still have to use .Net 4.5 objects (or third party tools links 7-zip if you don’t have .Net 4.5).

Tuesday, November 17, 2015

Write-Host is not as bad in PowerShell 5

We have been saying for years that Write-Host is bad. Never use it. Jeffrey Snover has said that one of his biggest regrets about PowerShell was Write-Host.

But nobody listens to me. And some people don’t even listen to Jeffrey. Write-Host continues to exist in a bazillion old and many new scripts. So the PowerShell team decided to fix it. Or make it less bad.

The problem with Write-Host was that it bypassed the output stream (and all of the other streams), and sent data directly to the “host” to be displayed on the screen. This meant that you couldn’t redirect the result and you couldn’t capture it in a variable. If you moved code whose only output was Write-Host into a function, you found it didn’t work, because only the streams are returned to the calling script. Plus, sometimes code runs in an environment where there is no host, at least not one that can understand what to do with Write-Host, so these scripts just fail.

The PowerShell team introduced a new stream in version 5, named Information, which comes with a Write-Information command (the analog to Write-Output, Write-Verbose, Write-Error, etc.). They decided to leverage this to redesign Write-Host, and rewrote is as a wrapper for Write-Information.

So now in PowerShell 5, Write-Host writes to the Information stream, which by default goes to the host and nowhere else.

There is still almost no reason to ever use Write-Host. It’s pretty useless. Usually writing to the Output stream is preferred or adequate, as it by default writes to the screen as well. And you can do that easily by just outputting something. “Message” does what you thought you were doing with Write-Host “Message”.

And if you do want to write to the Information stream instead of the Output stream, use Write-Information; that’s what it’s for.

The one thing that Write-Host can do that Write-Information can’t, is to write to the screen in pretty colors. Personally, I would prefer to enhance my scripts in other ways, like automatically handling extreme conditions, or using pretty reports where needed, or adding a GUI, or whatever, but if you want to write text to the screen in pretty colors, you now more safely use Write-Host for that.

Monday, November 16, 2015

Limiting recurse on Get-ChildItem in PowerShell 5

Sometimes when using Get-ChildItem, you want more than one but fewer than a near infinite number of results or you need it to take less than forever to return your results.

Let’s say you have a 10-terrabyte file share containing the home directories of thousands of users. You want to know who is storing .iso files and taking up lots of space unnecessarily.

If we use Get-ChildItem on the share, we aren’t going to get anything, because there are no files in the root path, only child folders. If we use Get-ChildItem with the -Recurse switch, it’s going to crawl through every subfolder of every subfolder of every subfolder… and it’s going to take three days, because someone let this share get way out of control, and Get-ChildItem is kind of slow.

We can, of course, write a simple script to scan a limited set of folders. But we’re scripters, and we’re lazy. Is there an easier way?

Starting in PowerShell 5, there is an easier way. They added a -Depth switch which simply tells it how far to recurse.

You can use a -Depth of 0, but that basically cancels out the -Recurse switch. You only get results from the root of your -Path.

001
Get-ChildItem \\FileServer1\HomeShare -Filter *.iso -Recurse -Depth 0


If you use a -Depth of 1, you search the root path, plus each of the folders in the root.

001
Get-ChildItem \\FileServer1\HomeShare -Filter *.iso -Recurse -Depth 1

With a -Depth of 2, you search the root path, each folder in the root, and each of their subfolders.

001
Get-ChildItem \\FileServer1\HomeShare -Filter *.iso -Recurse -Depth 2

In this hypothetical, I would just keep increasing the depth until I found a good balance point where I got as much good information as I could without taking too long to run.

Sunday, November 15, 2015

Native class definition in PowerShell 5 – a real life use case

With PowerShell 5, we can finally define .Net classes natively in PowerShell. In earlier versions, you would have to do fancy things with custom objects or write a class definition in C# and pipe it into Add-Type as a string.

The first reaction of a lot of PowerShellers out there is, “So what? What can a custom class do for me? Why would I ever need or want to use that?”

A lot of the early chatter about it on the web doesn’t answer that question. Some great PowerShell bloggers out there, like Trevor Sullivan and Ed Wilson, wrote some great blogs about how to do class definition that helped me get started. But nobody explained why. All of the examples I’ve seen out there are generic classes designed to teach concepts about classes, rather than give concrete examples of real life. You can find examples of a car class and a vehicle class and a beer class.

After playing on and off for a few months, I have finally had a few things click, and am starting to see where you can and should use your own custom classes.

Performance

As I’ve blogged about previously, code inside a class definition runs faster than the same code outside a class definition. PowerShell has a lot of flexibility in how we are allowed to do things. This is great. The cost is that it takes a lot of work and complexities at runtime to be able to handle whatever we throw at it. Because classes are more strictly defined than most PowerShell, not as much complexity is needed in the compiled runtime code, and as a result, it runs faster.

How much faster depends on the specific code. I have had test results all the way from no significant improvement to almost 20 times faster, depending on the code. Some of the test results can be found here.

Other than for performance, there usually won’t be a reason to use custom classes in your scripts. There are other ways of creating custom objects that are usually easier and perfectly adequate for your needs

Administration

Where classes can come in really handy is actually at the command line and in ad hoc admin scripts. I normally don’t use a PowerShell profile, because I don’t want to accidentally rely on something that exists on my machine in my profile that won’t exist on the production server where this script will run. But for these sorts of classes, I make an exception. These are things I will never use in a script. But they make life at the command line much easier.

I am going to talk through some custom classes that help us support a hypothetical environment where we have a large number of nearly identical groups of servers. In my hypothetical, this is an organization that has a large number of branches, each with the same 8 basic local servers, with only small differences in name and IP. This same concept could be used for supporting a large number of stores, or a large number of heterogeneous development environments, or whatever.

Enum

The first thing I am going to do is define an enumeration. An enumeration is a type, like a class is a type. An enumeration is a list of string values. Each value also has a unique integer value behind the scenes. You can define these integers explicitly if you need them to be something specific. By default, the integers are assigned incrementally starting at zero. The first value will be the default value if the question arises.

My enumeration is a list of the standard server types in each branch. This will be used to define the type of a server as well as provide a list of servers that need to be defined for a given branch. We do this with keyword “Enum”.

001
002
003
004
005
006
007
008
009
010
011
enum BranchServerType
    {
    DC
    EXC
    SQL
    FS1
    FS2
    AP1
    AP2
    HV
    }

Class definition

A class is defined using the keyword “class” followed by the name for the class followed by the definition in curly braces. To base your class on an existing class, follow the class name with a colon and the fully qualified name of the base class. If you omit an explicit base class, System.Object will be the base class.

001
002
class BranchServer
    {

Class properties

First we define the properties. For each property we give it a type and a name. We can also assign a default value.

001
002
003
004
    [string]$Name
    [string]$BranchName
    [BranchServerType]$Type
    [System.Net.IPAddress]$IPAddress

Note that we are using the enumeration we defined above as one of our property types. That property will be limited to one of the choices enumerated in the enumeration.

Static properties are not supported in PowerShell. Script properties are not support within the class keyword structure, but can be added later using Update-Type.

Class constructors

A constructor is a chunk of code that defines how to create a new instance of an object of this type. 

We can have more than one constructor defined. Definitions of constructors and methods are called “overloads” because of this capability of overloading them with more than one definition.

The restriction on overloads is that they have to take unique parameters—either a different number of parameters or different types of parameters—so that the system can figure out which definition to use when the constructor is called.

Our first constructor for BranchServer will take a branch name as a string, and a server type as a BranchServerType enumeration. At runtime we will can provide a string or integer for the BranchServerType enumeration, and as long as we won’t have any other constructors with the same number of parameters to confuse it, PowerShell will automatically convert it to the corresponding BranchServerServerType.

We provide the name of the constructor, which is always the same as the name as the class. Then the parameters, if any, in parenthesis, followed by the code definition in curly braces.

001
     BranchServer[string]$BranchName, [BranchServerType]$Type )

Within a class definition, there is a builtin variable named $This which refers to the object we are currently working on. Within a constructor, $This is the object we are constructing.

We are going to take the two parameters and put them directly into the matching class properties.

001
002
003
        {
        $This.BranchName = $BranchName
        $This.Type = $Type
 
Next I am using a Switch command to build the standard name of the server based on the BranchName and BranchServerType and our hypothetical standard naming convention.

001
002
003
004
005
006
007
008
009
010
011
012
         Switch ( $Type )
            {
            'DC'  { $This.Name = 'SV' + $BranchName + 'DC01' }
            'EXC' { $This.Name = 'SV' + $BranchName + 'EXC1' }
            'SQL' { $This.Name = 'SV' + $BranchName + 'SQL1' }
            'FS1' { $This.Name = 'SV' + $BranchName + 'FS01' }
            'FS2' { $This.Name = 'SV' + $BranchName + 'FS02' }
            'AP1' { $This.Name = 'SV' + $BranchName + 'AP01' }
            'AP2' { $This.Name = 'SV' + $BranchName + 'AP02' }
            'HV'  { $This.Name = 'SV' + $BranchName + 'HV01' }
            }
        }
 
Then we define a second constructor. This one takes three parameters, the BranchName and BranchServerType, plus an IP address. Again, at runtime we can provide a string at runtime for the IP address, and PowerShell will automatically convert it to a System.Net.IPAddress object. The constructor definition is the same as the first one, plus another Switch statement to determine the IPAddress for the server based on the first three octets of the IPAddress parameter and a hypothetical standard IP address allocation scheme.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
     BranchServer[string]$BranchName, [BranchServerType]$Type, [System.Net.IPAddress]$RootIPAddress )
        {
        $This.BranchName = $BranchName
        $This.Type = $Type
        Switch ( $Type )
            {
            'DC'  { $This.Name = 'SV' + $BranchName + 'DC01' }
            'EXC' { $This.Name = 'SV' + $BranchName + 'EXC1' }
            'SQL' { $This.Name = 'SV' + $BranchName + 'SQL1' }
            'FS1' { $This.Name = 'SV' + $BranchName + 'FS01' }
            'FS2' { $This.Name = 'SV' + $BranchName + 'FS02' }
            'AP1' { $This.Name = 'SV' + $BranchName + 'AP01' }
            'AP2' { $This.Name = 'SV' + $BranchName + 'AP02' }
            'HV'  { $This.Name = 'SV' + $BranchName + 'HV01' }
            }

        $BaseIPAddress = $RootIPAddress.IPAddressToString.Split( '.' )[0..2] -join '.'
        Switch ( $Type )
            {
            'DC'  { $This.IPAddress = $BaseIPAddress + '.1'   }
            'EXC' { $This.IPAddress = $BaseIPAddress + '.11'  }
            'SQL' { $This.IPAddress = $BaseIPAddress + '.21'  }
            'FS1' { $This.IPAddress = $BaseIPAddress + '.31'  }
            'FS2' { $This.IPAddress = $BaseIPAddress + '.32'  }
            'AP1' { $This.IPAddress = $BaseIPAddress + '.41'  }
            'AP2' { $This.IPAddress = $BaseIPAddress + '.42'  }
            'HV'  { $This.IPAddress = $BaseIPAddress + '.101' }
           }
        }

Class methods

Next we will define some methods. Methods are chunks of code built in to a class that usually do something with or to the object itself.

Methods, like constructors can take multiple definitions or “overloads”. (Constructors are basically a special type of method.) The definition syntax is mostly the same as a constructor.

We optionally specify the type of object that the method is going to return. If the method is not going to return anything, we use [void] as the return type. If we omit the return type, [void] is implied.

Then we give it a name, followed by any parameters in parenthesis, followed by the code definition in curly braces.

The first method takes no parameters. It derives the name of the Hyper-V server this server is hosted on based on this server’s name, and runs a Start-VM command against the host.

001
002
003
004
005
     Start()
        {
        $HV = $This.Name.Substring( 0, $This.Name.Length - 4 ) + 'HV01'
        Start-VM -VMName $This.Name -Server $HV
        }

A second definition or overload of the same method to allow the caller to specify a –Wait switch.

001
002
003
004
005
     Start[boolean]$Wait )
        {
        $HV = $This.Name.Substring( 0, $This.Name.Length - 4 ) + 'HV01'
        Start-VM -VMName $This.Name -Server $HV -Wait:$Wait
        }

A method to stop the server.

001
002
003
004
     Stop()
        {
        Stop-Computer -ComputerName $This.Name
        }

A method to restart the server.

001
002
003
004
    Restart()
        {
        Restart-Computer -ComputerName $This.Name
        }

A method to launch an RDP connection to the server.

001
002
003
004
     RDP()
        {
        mstsc /v:$($This.Name)
        }

And last, a method to enter a PSSession to the server.

001
002
003
004
005
     PSSession()
        {
        Enter-PSSession -ComputerName $This.Name
        }
    }

Another class

Now let’s build a branch. A branch has a name, and an array containing a bunch of servers.

001
002
003
004
class Branch
    {
    [string]$Name
    [BranchServer[]]$Servers

Our first constructor simply takes a branch name. It loops through the enumaration of BranchServerTypes, and calls the constructor for BranchServer for each of them, resulting in an array of all 8 servers in the branch.

001
002
003
004
005
006
007
008
     Branch ( [string]$Name )
        {
        $This.Name = $Name
        ForEach ( $Type in [BranchServerType].GetEnumValues() )
            {
            $This.Servers += [BranchServer]::New( $Name, $Type )
            }
        }

A second constructor does the same thing, but includes IP addresses.

001
002
003
004
005
006
007
008
     Branch ( [string]$Name, [System.Net.IPAddress]$RootIPAddress )
        {
        $This.Name = $Name
        ForEach ( $Type in [BranchServerType].GetEnumValues() )
            {
            $This.Servers += [BranchServer]::New( $Name, $Type, $RootIPAddress )
            }
        }

A method to return the server of a given type. So I can ask the Branch object for the SQL server, and it will give me the BranchServer object for the SQL server in that branch. The [BranchServer] type appears before the method name because that is the type of object the method returns.

001
002
003
004
     [BranchServer] Server ( [BranchServerType]$Type )
        {
        Return ( $This.Servers | Where Type -eq $Type )
        }

And then finally, just to show how fancy we can get, a method to restart the host. This method gets all of the running servers, and just for fun since we are in PowerShell 5 anyway, uses some fancy syntax to split them into domain controllers, SQL servers, and other servers. It suspends the other servers, then the SQL servers and then the domain controllers. Then it restarts the host. Then is starts the domain controllers, and then the SQL servers, and then the remaining servers.

Please note: I know this method may not work exactly as intended. It’s a demo draft of a hypothetical scenario. Plus, if this were going to be used in real life, I would add a lot more error handling and idiot handling. This is just to give you an idea of what’s possible.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
     RestartHost ()
        {
        $HVHost  = ( $This.Servers | Where Type -eq 'HV' ).Name
        $DCVM,  $OtherVM = ( Get-VM -Server $HVHost ).Where{ $_.State -eq 'Running' }.Where( { $_.Name -like "*DC*" }, 'Split' )
        $SQLVM, $OtherVM = $OtherVM.Where( { $_.Name -like "*SQL*" }, 'split' )
        $OtherVM | Suspend-VM
        $SQLVM   | Suspend-VM
        $DCVM    | Suspend-VM
        Restart-Computer $HVHost -Wait -For PowerShell
        $DCVM    | Start-VM
        $SQLVM   | Start-VM
        $OtherVM | Start-VM
        }
    }
 
Using the objects based on our defined classes

After running those definitions, using them is easy. We can create a [Branch] object, fully populated with all the information about every server in the branch, with a single line.

001
$B = [Branch]::New( 'B123', '10.1.2.0' )

To see a full list of the servers in the branch, we simply use the .Servers property

001
$B.Servers

Launching an RDP session to the branch domain controller is as easy as this.

001
$B.Server( 'DC' ).RDP()

And to enter a remote PSSession on the branch app server 1, we do it like this.

001
$B.Server( 'AP1' ).Restart()

Or we can do it without creating the object first. We just type this at the command line.

001
([Branch]'B234').Server('SQL').PSSession()

The full script defining the custom enum and classes

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
enum BranchServerType
    {
    DC
    EXC
    SQL
    FS1
    FS2
    AP1
    AP2
    HV
    }

class BranchServer
    {
    [string]$Name
    [string]$BranchName
    [BranchServerType]$Type
    [System.Net.IPAddress]$IPAddress

    BranchServer[string]$BranchName, [BranchServerType]$Type )
        {
        $This.BranchName = $BranchName
        $This.Type = $Type
        Switch ( $Type )
            {
            'DC'  { $This.Name = 'SV' + $BranchName + 'DC01' }
            'EXC' { $This.Name = 'SV' + $BranchName + 'EXC1' }
            'SQL' { $This.Name = 'SV' + $BranchName + 'SQL1' }
            'FS1' { $This.Name = 'SV' + $BranchName + 'FS01' }
            'FS2' { $This.Name = 'SV' + $BranchName + 'FS02' }
            'AP1' { $This.Name = 'SV' + $BranchName + 'AP01' }
            'AP2' { $This.Name = 'SV' + $BranchName + 'AP02' }
            'HV'  { $This.Name = 'SV' + $BranchName + 'HV01' }
            }
        }

    BranchServer[string]$BranchName, [BranchServerType]$Type, [System.Net.IPAddress]$RootIPAddress )
        {
        $This.BranchName = $BranchName
        $This.Type = $Type
        Switch ( $Type )
            {
            'DC'  { $This.Name = 'SV' + $BranchName + 'DC01' }
            'EXC' { $This.Name = 'SV' + $BranchName + 'EXC1' }
            'SQL' { $This.Name = 'SV' + $BranchName + 'SQL1' }
            'FS1' { $This.Name = 'SV' + $BranchName + 'FS01' }
            'FS2' { $This.Name = 'SV' + $BranchName + 'FS02' }
            'AP1' { $This.Name = 'SV' + $BranchName + 'AP01' }
            'AP2' { $This.Name = 'SV' + $BranchName + 'AP02' }
            'HV'  { $This.Name = 'SV' + $BranchName + 'HV01' }
            }

        $BaseIPAddress = $RootIPAddress.IPAddressToString.Split( '.' )[0..2] -join '.'
        Switch ( $Type )
            {
            'DC'  { $This.IPAddress = $BaseIPAddress + '.1'   }
            'EXC' { $This.IPAddress = $BaseIPAddress + '.11'  }
            'SQL' { $This.IPAddress = $BaseIPAddress + '.21'  }
            'FS1' { $This.IPAddress = $BaseIPAddress + '.31'  }
            'FS2' { $This.IPAddress = $BaseIPAddress + '.32'  }
            'AP1' { $This.IPAddress = $BaseIPAddress + '.41'  }
            'AP2' { $This.IPAddress = $BaseIPAddress + '.42'  }
            'HV'  { $This.IPAddress = $BaseIPAddress + '.101' }
           }
        }

    Start()
        {
        $HV = $This.Name.Substring( 0, $This.Name.Length - 4 ) + 'HV01'
        Start-VM -VMName $This.Name -Server $HV
        }

    Start[boolean]$Wait )
        {
        $HV = $This.Name.Substring( 0, $This.Name.Length - 4 ) + 'HV01'
        Start-VM -VMName $This.Name -Server $HV -Wait:$Wait
        }

    Stop()
        {
        Stop-Computer -ComputerName $This.Name
        }

    Restart()
        {
        Restart-Computer -ComputerName $This.Name
        }

    RDP()
        {
        mstsc /v:$($This.Name)
        }

    PSSession()
        {
        Enter-PSSession -ComputerName $This.Name
        }
    }

class Branch
    {
    [string]$Name
    [BranchServer[]]$Servers

    Branch ( [string]$Name )
        {
        $This.Name = $Name
        ForEach ( $Type in [BranchServerType].GetEnumValues() )
            {
            $This.Servers += [BranchServer]::New( $Name, $Type )
            }
        }

    Branch ( [string]$Name, [System.Net.IPAddress]$RootIPAddress )
        {
        $This.Name = $Name
        ForEach ( $Type in [BranchServerType].GetEnumValues() )
            {
            $This.Servers += [BranchServer]::New( $Name, $Type, $RootIPAddress )
            }
        }

    [BranchServer] Server ( [BranchServerType]$Type )
        {
        Return ( $This.Servers | Where Type -eq $Type )
        }

    RestartHost ()
        {
        $HVHost  = ( $This.Servers | Where Type -eq 'HV' ).Name
        $DCVM,  $OtherVM = ( Get-VM -Server $HVHost ).Where{ $_.State -eq 'Running' }.Where( { $_.Name -like "*DC*" }, 'Split' )
        $SQLVM, $OtherVM = $OtherVM.Where( { $_.Name -like "*SQL*" }, 'split' )
        $OtherVM | Suspend-VM
        $SQLVM   | Suspend-VM
        $DCVM    | Suspend-VM
        Restart-Computer $HVHost -Wait -For PowerShell
        $DCVM    | Start-VM
        $SQLVM   | Start-VM
        $OtherVM | Start-VM
        }
    }