PowerShell DSC: IPv6 cIPv6NicBinding

This post covers the details of my PowerShell Desired State Configuration cIPv6NicBinding Resource, version 1. My next post will follow up with version 2 of this Resource, which was written using the Windows Management Framework 5.0 Class based functionality for Desired State Configuration Resources.


I know it’s still a somewhat common practice for internal servers to have IPv6 unbound from their network adapters. A Desired State Configuration Resource would be the simplest way of handling this when building new servers.

To build the Resource I figured that identifying bound IP addresses using WMI was going to be a nice backward-compatible process, and using Microsoft’s nvspbind.exe was also a nice backward-compatible way to manage the IPv6 binding. I came across nvspbind.exe when attempting to script the unbinding of IPv6 on my first Hyper-V 2012 (Server Core on Windows Server 2012) farm and it was so simple to use I thought why not re-use it here. It probably isn’t ideal to have a file pre-requisite for the Resource but it works (note my updated Class based Resource removes this pre-requisite).


Before getting into the cIPv6NicBinding resource, here’s the File resource I use to deploy nvspbind.exe from an internal file share (note I manually downloaded the executable from Microsoft as my servers do not have internet access). Nvspbind.exe is an executable written by Keith Mange of Microsoft’s Hyper-V team back in January 2010 to overcome protocol binding issues with Server Core installations of Windows Server 2008 and Windows Server 2008 R2. More information on nvspbind.exe can be found on .

File nvspbind
{
    Ensure            = 'Present'
    SourcePath        = "$sourcepath\nvspbind.exe"
    DestinationPath   = "$env:windir\System32\"
    Type              = 'File'
}

As you can see, this is nothing out of the ordinary, just a standard File resource.


Now back to my cIPv6Nicbinding Desired State Configuration Resource. I’ll start with the single helper function I’m using, Test-IPv6Enabled. This function checks whether the specified Network Adapter has a valid IPv6 binding and returns ‘Yes’ or ‘No’.

Function Test-IPv6Enabled
{
    $NA = Get-WmiObject -Class Win32_NetworkAdapter
    $NeNACtworkAdapterConfiguration = Get-WmiObject -Class Win32_NetworkAdapterConfiguration
    $MACAddress = ($NA | Where-Object {$_.NetConnectionID -eq $InterfaceAlias}).MACAddress
    $IpAddresses = ($NAC | Where-Object {$_.MACAddress -eq $MACAddress}).IPAddress

    $retVal = 'No'
    ForEach ($IP in $IpAddresses)
    {
        If (([System.Net.IPAddress]$IP).AddressFamily -eq 'InterNetworkV6')
        {
            $retVal = 'Yes'
        }
    }
    return $retVal
}

This function uses the WMI class Win32_NetworkAdapter to retrieve the MAC Address of the Network Adapter with the specified InterfaceAlias. With the MAC Address in hand we can use the WMI class Win32_NetworkAdapterConfiguration to retrieve the Network Adapters active IP Addresses. This process is required as the Win32_NetworkAdapterConfiguration class doesn’t include the InterfaceAlias in any of the returned properties.

Once the IP Addresses are retrieved, we’ll pass them through the .NET System.Net.IPAddres class to return the AddressFamily property. ‘InterNetwork’ equals an IPv4 address while ‘InterNetworkV6’ equals an IPv6 address.

This function will be used in the logic of our Resource to test whether there is a valid IPv6 binding or not.


The three parameters used in this Desired State Configuration Resource can be found in the in the cIPv6NicBinding.schema.mof file.

InterfaceAlias is the name of the network adapter (this is simply the network adapters name as seen in the Windows GUI), IPv6Enabled is a ‘Yes’/’No’ value specifying whether you want IPv6 to be bound to the specified network adapter or not, nvspbind is the path to the nvspbind executable as pushed out with the File Resource above (the full path, including ‘nvspbind.exe’).

[ClassVersion("1.0.0"), FriendlyName("cIPv6NicBinding")]
class cIPv6NicBinding : OMI_BaseResource
{
    [Key, Description("Network Adapter Name")] string InterfaceAlias;
    [Write, Description("Enable or disable IPv6"),ValueMap{"Yes", "No"},Values{"Yes", "No"}] string IPv6Enabled;
    [Write, Description("Path to nvspbind executable")] string nvspbind;
};

The Get-TargetResource function is nothing special. The primary check here uses the Test-IPv6Enabled helper function to return a ‘Yes’/’No’ value of whether the specified Network Adapter has a valid IPv6 IP address.

We then create a hashtable with the same parameters as the Resource and return it.

Function Get-TargetResource
{
    [OutputType([Hashtable])]
    param
    (
        # Network Adapter Name
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]
        $InterfaceAlias,

        # Enable or disable the supplied configuration
        [Parameter(Mandatory)]
        [ValidateSet('Yes', 'No')]
        [String]
        $IPv6Enabled,

        # Path to nvspbind executable
        [Parameter(Mandatory)]
        [String]
        $nvspbind
    )

    Write-Verbose "GET: Get IP Addresses for the Network Interface [$InterfaceAlias]"
    $ActiveIPv6Status = Test-IPv6Enabled

    $returnValue = @{
        NicName     = $InterfaceAlias
        IPv6Enabled = $ActiveIPv6Status
        nvspbind    = $nvspbind
    }

    return $returnValue
}

The Test-TargetResource function simply returns a $true/$false based on whether the called $IPv6Enabled parameter equals the result of the helper function Test-IPv6Enabled.

Function Test-TargetResource
{
    [OutputType([Boolean])]
    param
    (
        # Firewall Group Name
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $InterfaceAlias,

        # Enable or disable the supplied configuration
        [ValidateSet('Yes', 'No')]
        [String]
        $IPv6Enabled,

        # Path to nvspbind executable
        [String]
        $nvspbind
    )

    If ($IPv6Enabled -eq (Test-IPv6Enabled)) {$returnValue = $true }
    Else                                     {$returnValue = $false}

    Write-Verbose "TEST: Returning $returnValue"
    return $returnValue
}

The Set-TargetResource function needs to retrieve the Network Adapters GUID as that’s required when calling nvspbind.exe. Next up, nvspbind.exe is launched to enable or disable the IPv6 binding.

A number of lines in this function are to provide -WhatIf support, which should be included in all Set-TargetResource functions.

Function Set-TargetResource
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        # Firewall Group Name
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $InterfaceAlias,

        # Enable or disable the supplied configuration
        [ValidateSet('Yes', 'No')]
        [String]
        $IPv6Enabled,

        # Path to nvspbind executable
        [String]
        $nvspbind
    )

    $NA = Get-WmiObject -Class Win32_NetworkAdapter
    $GUID = ($NA | Where-Object {$_.NetConnectionID -eq $InterfaceAlias}).GUID

    # Text output if '-WhatIf' is called
    If     ($IPv6Enabled -eq 'Yes') {$WhatIf = $InterfaceAlias}
    ElseIf ($IPv6Enabled -eq 'No')  {$WhatIf = $InterfaceAlias}

    If ($PSCmdlet.ShouldProcess($WhatIf))
    {
        If ($IPv6Enabled -eq 'Yes')
        {
            Write-Verbose "Set: Enabling IPv6 on the [$InterfaceAlias] Network Adapter"
            Start-Process -FilePath $nvspbind -ArgumentList "-e $GUID ms_tcpip6" -NoNewWindow -Wait
        }
        ElseIf ($IPv6Enabled -eq 'No')
        {
            Write-Verbose "Set: Disabling IPv6 on the [$InterfaceAlias] Network Adapter"
            Start-Process -FilePath $nvspbind -ArgumentList "-d $GUID ms_tcpip6" -NoNewWindow -Wait
        }
    }
}

I won’t provide details of the Module Manifest file as it’s a standard affair, created with the New-ModuleManifest cmdlet. The Module Manifest is included in the 7zip file linked at the bottom of this post.

Here’s an example Desired State Configuration for this Resource:

Configuration IPv6NicBindingTest
{
    Param
    (
        [Parameter(Mandatory)]
        [string]
        $ComputerName
    )

    Import-DscResource -Name cIPv6NicBinding

    node ($ComputerName) {
        File nvspbind {
            Ensure            = 'Present'
            SourcePath        = '\\fileserver\share\nvspbind\nvspbind.exe'
            DestinationPath   = "$env:windir\System32\"
            Type              = 'File'
        }

        cIPv6NicBinding ipv6binding {
            InterfaceAlias = 'Local Area Connection'
            IPv6Enabled    = 'No'
            nvspbind       = "$env:windir\System32\nvspbind.exe"
            DependsOn      = '[File]nvspbind'
        }
    }
}

IPv6NicBindingTest -ComputerName localhost -OutputPath C:\Scripts
Start-DscConfiguration -ComputerName localhost -Path C:\Scripts -Verbose -Wait

When running the above Desired State Configuration this was the consoles output.

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName'
= root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer SVR2012R2 with user sid S-1-5-21-4259290700-248647520-2809069105-1001.
VERBOSE: [SVR2012R2]: LCM:  [ Start  Set      ]
VERBOSE: [SVR2012R2]: LCM:  [ Start  Resource ]  [[File]nvspbind]
VERBOSE: [SVR2012R2]: LCM:  [ Start  Test     ]  [[File]nvspbind]
VERBOSE: [SVR2012R2]:                            [[File]nvspbind] The related file/directory is: C:\Windows\System32\.
VERBOSE: [SVR2012R2]:                            [[File]nvspbind] Building file list from cache.
VERBOSE: [SVR2012R2]: LCM:  [ End    Test     ]  [[File]nvspbind]  in 0.0150 seconds.
VERBOSE: [SVR2012R2]: LCM:  [ Start  Set      ]  [[File]nvspbind]
VERBOSE: [SVR2012R2]:                            [[File]nvspbind] The related file/directory is: C:\Windows\System32\.
VERBOSE: [SVR2012R2]:                            [[File]nvspbind] Building file list from cache.
VERBOSE: [SVR2012R2]:                            [[File]nvspbind] Copying file \\fileserver\share\nvspbind\nvspbind.exe to C:\Windows\System32\nvspbind.exe.
VERBOSE: [SVR2012R2]: LCM:  [ End    Set      ]  [[File]nvspbind]  in 0.0000 seconds.
VERBOSE: [SVR2012R2]: LCM:  [ End    Resource ]  [[File]nvspbind]
VERBOSE: [SVR2012R2]: LCM:  [ Start  Resource ]  [[cIPv6NicBinding]ipv6binding]
VERBOSE: [SVR2012R2]: LCM:  [ Start  Test     ]  [[cIPv6NicBinding]ipv6binding]
VERBOSE: [SVR2012R2]:                            [[cIPv6NicBinding]ipv6binding] TEST: Returning False
VERBOSE: [SVR2012R2]: LCM:  [ End    Test     ]  [[cIPv6NicBinding]ipv6binding]  in 0.1410 seconds.
VERBOSE: [SVR2012R2]: LCM:  [ Start  Set      ]  [[cIPv6NicBinding]ipv6binding]
VERBOSE: [SVR2012R2]:                            [[cIPv6NicBinding]ipv6binding] Performing the operation "Set-TargetResource" on target "Local Area Connection".
VERBOSE: [SVR2012R2]:                            [[cIPv6NicBinding]ipv6binding] Set: Disabling IPv6 on the [Local Area Connection] Network Adapter
VERBOSE: [SVR2012R2]: LCM:  [ End    Set      ]  [[cIPv6NicBinding]ipv6binding]  in 1.1110 seconds.
VERBOSE: [SVR2012R2]: LCM:  [ End    Resource ]  [[cIPv6NicBinding]ipv6binding]
VERBOSE: [SVR2012R2]: LCM:  [ End    Set      ]    in  1.3460 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 1.373 seconds

So there you have it – my Desired State Configuration Resource: cIPv6NicBinding. The full Resource (including the Manifest) can be found here. I hope you find this Resource helpful!

If anyone in the community has any feedback or advice I’d be more than happy for some peer review. And yes, one day I’ll get round to setting up a github repository to store my Resources on. One day...

Exporting data to Excel

This post is to provide some background into a business solution I had to solve recently. While the code block isn't something I derived myself it definitely came to my help, thanks nixda! I want to ensure others can benefit from my troubles in finding a nice way of exporting data from PowerShell to Excel.

Due to a system upgrade, auditors required SQL data comparisons on a frequent basis - my customers life was going to be very painful doing this manually...

The process was to retrieve SQL data from two systems, compare the data to find any mismatches, export the comparison results into an Excel spreadsheet, provide some traffic lights for easy identification of which data sets matched up, and finally email the Excel file to the customer so it could be used as both an internal check and something that could be forwarded to the auditors. All in all I think the end result was great, a 5 minute script the customer could run rather than a multi-day manual comparison.

However, in writing this script I found the process to export arrays into Excel was actually quite painful! My initial thought was to format the data from PowerShell using format-table, then pipe this into the clipboard (using 'clip.exe') so the data was available for pasting. #FAIL. Well it did work, except the data was (in hindsight, obviously) copied simply as text. No formatting, no nice way of placing this data into excel columns easily.

Next!

Perhaps I could simply process each row/column of excel and write the data from the arrays in, one record at a time. I've used this process before so I knew it would work - however I've only ever needed to do this using VBA, not from PowerShell using Excel com objects.

I coded it and it worked, and would've provided the required result for the customer; however it was terribly slow. The writing of data averaged out to around one cell of data every second! Would it provide the correct outcome? Yes! Would the performance be good enough for business? No. There was no room in my mind for this script to be a multi-hour process.

Next!

At this point I started hunting around the internet to see what other people had coded to see if there was anything to jump start my thinking: "powershell export data to excel", "powershell paste clipboard to excel", perhaps "export powershell csv to excel" <- winner!

From that search I came across a very useful response to a question on stackoverflow.com by the user nixda. Here is the code as posted:

### Set input and output path
$inputCSV = "C:\somefolder\input.csv"
$outputXLSX = "C:\somefolder\output.xlsx"

### Create a new Excel Workbook with one empty sheet
$excel = New-Object -ComObject excel.application
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)

### Build the QueryTables.Add command
### QueryTables does the same as when clicking "Data » From Text" in Excel
$TxtConnector = ("TEXT;" + $inputCSV)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)

### Set the delimiter (, or ;) according to your regional settings
$query.TextFileOtherDelimiter = $Excel.Application.International(5)

### Set the format to delimited and text for every column
### A trick to create an array of 2s is used with the preceding comma
$query.TextFileParseType  = 1
$query.TextFileColumnDataTypes = ,2 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1

### Execute & delete the import query
$query.Refresh()
$query.Delete()

### Save & close the Workbook as XLSX. Change the output extension for Excel 2003
$Workbook.SaveAs($outputXLSX,51)
$excel.Quit()

Well this definitely looks positive! I already had a bunch of this code; to create the Excel object, a new WorkBook and WorkSheet, however the section from lines 10 through 27 looked to be exactly what I was after. The end result was to export my hashtable to a .csv file, then import that file using Excel's QueryTables functionality to perform this really quickly.

I know this block isn't my code but I wanted to share it again in case anyone else had a similar problem to solve.

Since proving my solution to business, I saw a tweet from Doug Finke's (@dfinke) linking to his PowerShell module for creating Excel spreadsheets (with pivot tables and charts) without Excel being installed. Here are the links to Doug's tweet and github repository for this module. While I haven't tried this module yet I'm sure it could've made my life easier.

PowerShell DSC: cLegacyFirewall Helper Functions

Almost done with my cLegacyFirewall Desired State Configuration (DSC) Resource. There are a few other helper functions required to make this Resource work correctly, and they're primarily modified versions of those supplied in the PowerShell DSC Waves 'xNetworking' - 'xFirewall' Resource. I will also touch on the schema.mof and Module Manifest files.

First up is Test-RuleHasProperties. This function validates firewall rules that exist on the target machine against that called in the Resource; for example from this test, DSC will know whether the 'Set-TargetResource' function is required.

# Function to validate if the supplied Rule adheres to all parameters set
Function Test-RuleHasProperties
    {
        param
        (
            [Parameter(Mandatory)]
            $FirewallRule,

            [String]
            $DisplayName,

            [String]$Enabled,

            [String]
            $Direction,

            $Profiles,

            $LocalIP,

            $RemoteIP,

            [String]
            $Program,
            $LocalPort,

            $RemotePort,

            $Protocol,

            [String]
            $Action
        )

        $desiredConfigurationMatch = $true

        if ($DisplayName -and ($FirewallRule.'Rule Name' -ne $DisplayName))
        {
            Write-Verbose "Function: Test-RuleHasProperties: DisplayName property value - $FirewallRule.'Rule Name' does not match desired state - $DisplayName"

            $desiredConfigurationMatch = $false
        }

        if ($Enabled -and ($FirewallRule.Enabled -ne $Enabled))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Enabled property value - $($FirewallRule.Enabled) does not match desired state - $Enabled"

            $desiredConfigurationMatch = $false
        }

        if ($Direction -and ($FirewallRule.Direction -ne $Direction))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Direction property value - $($FirewallRule.Direction) does not match desired state - $Direction"

            $desiredConfigurationMatch = $false
        }

        if ($Profiles -eq "Any")
        {
            if ($Profiles -and ($FirewallRule.Profiles -ne "Domain,Private,Public"))
            {
                Write-Verbose "Function: Test-RuleHasProperties: Profiles property value - $($FirewallRule.Profiles) does not match desired state - $Profiles"

                $desiredConfigurationMatch = $false
            }
        }
        elseif ($Profiles -and ($FirewallRule.Profiles -ne $Profiles))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Profiles property value - $($FirewallRule.Profiles) does not match desired state - $Profiles"

            $desiredConfigurationMatch = $false
        }

        if ($LocalIP -and ($FirewallRule.LocalIP -ne $LocalIP))
        {
            Write-Verbose "Function: Test-RuleHasProperties: LocalIP property value - $($FirewallRule.LocalIP) does not match desired state - $LocalIP"

            $desiredConfigurationMatch = $false
        }

        if ($RemoteIP -and ($FirewallRule.RemoteIP -ne $RemoteIP))
        {
            Write-Verbose "Function: Test-RuleHasProperties: RemoteIP property value - $($FirewallRule.RemoteIP) does not match desired state - $RemoteIP"

            $desiredConfigurationMatch = $false
        }

        if ($Program -and ($FirewallRule.Program -ne $Program))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.Program) does not match desired state - $Program"

            $desiredConfigurationMatch = $false
        }

        if ($LocalPort -and ($FirewallRule.LocalPort -ne $LocalPort))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.LocalPort) does not match desired state - $LocalPort"

            $desiredConfigurationMatch = $false
        }

        if ($RemotePort -and ($FirewallRule.RemotePort -ne $RemotePort))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.RemotePort) does not match desired state - $RemotePort"

            $desiredConfigurationMatch = $false
        }

        if ($Protocol -and ($FirewallRule.Protocol -ne $Protocol))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Program property value - $($FirewallRule.Protocol) does not match desired state - $Protocol"

            $desiredConfigurationMatch = $false
        }

        if ($Action -and ($FirewallRule.Action -ne $Action))
        {
            Write-Verbose "Function: Test-RuleHasProperties: Action property value - $($FirewallRule.Action) does not match desired state - $Action"

            $desiredConfigurationMatch = $false
        }

        Write-Verbose "Function: Test-RuleHasProperties returning $desiredConfigurationMatch"
        return $desiredConfigurationMatch
    }
}

As mentioned above the code, this simply test every property of the firewall rule, and if any do not match those specified in the configuration, the $desiredConfigurationMatch variable is set to $false; which is then returned back.

The only other helper function used is Add-cLegacyFirewallRule. This function builds the required 'netsh' command line to create the rule from DSC; then calls that netsh command to create the rule.

    Function Add-cLegacyFWRule {
        # Set the Firewall rule based on specified parameters

        $RunCommand = "netsh advfirewall firewall add rule"
        If ($Displayname)         {$RunCommand += " name=`"$Displayname`""}
        If ($Direction)           {$RunCommand += " dir=$Direction"}
        If ($Action)              {$RunCommand += " action=$Action"}
        If ($Enabled)             {$RunCommand += " enable=$Enabled"}
        If ($LocalIP -ne "Any")   {$RunCommand += " localip=$LocalIP"}
        If ($RemoteIP -ne "Any")  {$RunCommand += " remoteip=$RemoteIP"}
        If ($Profiles)            {$RunCommand += " profile=$Profiles"}
        If ($Program)             {$RunCommand += " program=`"$Program`""}
        If ($LocalPort)           {$RunCommand += " localport=`"$LocalPort`""}
        If ($RemotePort)          {$RunCommand += " remoteport=`"$RemotePort`""}
        If ($Protocol)            {$RunCommand += " protocol=$Protocol"}

        $retVal = Invoke-Expression -Command:$RunCommand

        return $retVal
    }

The remaining requirements for this Desired State Configuration Resource are the Module Manifest, and the schema.mof file. The layout of these is pretty straight forward.

The Module Manifest 'shell' can be created with the "New-ModuleManifest" cmdlet, this is the simplest form of the command and you can then simply modify the text file as required:

New-ModuleManifest -Path 'Path\to\DSCResource'

My manifest can be found in my Resource, which is attached at the end of this post.

The schema.mof file (in this Resource, its called cLegacyFirewall.schema.mof) outlines the DSC Resources parameters. Think of it in a similar way to the 'param' sections of the Get/Set/Test-TargetResource functions, however it is the schematic for the Resource class, eg:

[ClassVersion("1.0.0"), FriendlyName("cLegacyFirewall")]
class cLegacyFirewall : OMI_BaseResource
{
[Key, Description("Display Name of firewall rule")] string DisplayName;
[Write, Description("Enable the firewall rule"),ValueMap{"Yes", "No"},Values{"Yes", "No"}] string Enabled;
[Write, Description("Direction of the connection"),ValueMap{"In", "Out"},Values{"In", "Out"}] string Direction;
[Write, Description("Profile for firewall rule"),ValueMap{"Any","Domain","Private","Public"},Values{"Any","Domain","Private","Public"}] string Profiles;
[Write, Description("Local IP Restrictions")] string LocalIP;
[Write, Description("Remote IP Restrictions")] string RemoteIP;
[Write, Description("Program Path")] string Program;
[Write, Description("Local Port")] string LocalPort;
[Write, Description("Remote Port")] string RemotePort;
[Write, Description("Protocol")] string Protocol;
[Write, Description("Rule action"),ValueMap{"Allow", "Block", "Bypass"},Values{"Allow", "Block", "Bypass"}] string Action;
[Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
};

So there you go, that's the end of the posts regarding my cLegacyFirewall Desired State Configuration Resource; I hope you've found these posts useful and have learnt something about creating your own DSC Resources.

This particular resource can be found here, I haven't created a github repository to store my Resources yet, although I really should! This Resource may be making it's way into one of the PowerShell communities github repository, however I'll wait to announce that until it's happened.

PowerShell DSC: cLegacyFirewall Test/Set Functions

This is the (very slowly written) follow up to my cLegacyFirewall Get-TargetResource post. This will cover off the two remaining DSC 'TargetResource' requirements for my cLegacyFirewall Desired State Configuration (DSC) Resource, however a final post is coming with the remaining helper functions.


The Test-TargetResource will check whether the specified firewall rule currently exists on the machine, and is used by DSC to check whether the Set-TargetResource function is required. This function is very similar to the Get-TargetResource, with the primary difference being it checks the properties of existing firewall rules to ensure all properties line up with the rule DSC is trying to create (/remove).

Here's the code for Test-TargetResource:

Function Test-TargetResource
{
    [OutputType([Boolean])]
    param
    (
        # Localized, user-facing name of the Firewall Rule being created
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$DisplayName,

        # Enable or disable the supplied configuration
        [ValidateSet("Yes", "No")]
        [String]$Enabled = "Yes",

        # Direction of the connection
        [ValidateSet("In", "Out")]
        [String]$Direction = "In",

        # Specifies one or more profiles to which the rule is assigned
        [ValidateSet("Any", "Public", "Private", "Domain")]
        [String]$Profiles = "Any",

        # Local IP used for the filter
        $LocalIP = "Any",

        # Remote IP used for the filter
        $RemoteIP = "Any",

        # Path and file name of the program for which the rule is applied
        [String]$Program,

        # Local Port used for the filter
        $LocalPort,

        # Remote Port used for the filter
        $RemotePort,

        # IP Protocol used for the filter
        $Protocol,

        # Permit or Block the supplied configuration 
        [ValidateSet("Bypass", "Allow", "Block")]
        [String]$Action,

        # Ensure the presence/absence of the resource
        [ValidateSet("Present", "Absent")]
        [String]$Ensure = "Present"
    )

    Write-Verbose "TEST: Find rules with specified parameters"
    $FirewallRules = Get-cLegacyFWRule -DisplayName $DisplayName

    # Note 1
    if (!($FirewallRules.'Rule Name'))
    {
        Write-Verbose "TEST: Firewall Rule does not exist"

        $ReturnValue = (($Ensure -eq "Present") -eq $false)

        return $ReturnValue
    }

    $exists = $true
    $valid = $true
    foreach ($FirewallRule in $FirewallRules)
    {
        Write-Verbose "TEST: Check each defined parameter against the existing firewall rule [$($firewallRule.'Rule Name')]"
        # Note 2
        if (Test-RuleHasProperties -FirewallRule $FirewallRule `
                                    -DisplayName $DisplayName `
                                    -Enabled $Enabled `
                                    -Direction $Direction `
                                    -Profiles $Profiles `
                                    -LocalIP $LocalIP `
                                    -RemoteIP $RemoteIP `
                                    -Program $Program `
                                    -LocalPort $LocalPort `
                                    -RemotePort $RemotePort `
                                    -Protocol $Protocol `
                                    -Action $Action )
        {
        }
        else
        {
            $valid = $false
        }
    }

    # Returns whether or not $exists complies with $Ensure
    $ReturnValue = ($valid -and $exists -eq ($Ensure -eq "Present"))

    Write-Verbose "TEST: Returning $ReturnValue"

    return $ReturnValue
}

The primary sections to note:

The IF statement at "# Note 1" is used if the calling Firewall Rule does not exist. It will return $true/$false depending on the $Ensure value. eg if $Ensure = Present, and the Firewall Rule doesn't exist, the function will return $false. DSC would interpret this to mean the Set-TargetResource function is required to create the Firewall Rule.

The code at "# Note 2" will test the found Firewall Rules against the calling DSC Rule to confirm whether the property values line up. This code sets the $valid variable depending whether the properties line up correctly or not.

This code at "# Returns whether or not $exists complies with $Ensure" will return a $true/$false dependent on the existence of Firewall Rules, the $valid state of the rule and whether $Ensure = Present/Absent.

The Set-TargetResource function will create or delete the Firewall Rule being called, dependent on the value of $Ensure. The code is below:

Function Set-TargetResource
{
    param
    (
        # Localized, user-facing name of the Firewall Rule being created
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$DisplayName,

        # Enable or disable the supplied configuration
        [ValidateSet("Yes", "No")]
        [String]$Enabled = "Yes",

        # Direction of the connection
        [ValidateSet("In", "Out")]
        [String]$Direction = "In",

        # Specifies one or more profiles to which the rule is assigned
        [ValidateSet("Any", "Public", "Private", "Domain")]
        [String]$Profiles = "Any",

        # Local IP used for the filter
        $LocalIP = "Any",

        # Remote IP used for the filter
        $RemoteIP = "Any",

        # Path and file name of the program for which the rule is applied
        [String]$Program,

        # Local Port used for the filter
        $LocalPort,

        # Remote Port used for the filter
        $RemotePort,

        # IP Protocol used for the filter
        $Protocol,

        # Permit or Block the supplied configuration 
        [ValidateSet("Bypass", "Allow", "Block")]
        [String]$Action,

        # Ensure the presence/absence of the resource
        [ValidateSet("Present", "Absent")]
        [String]$Ensure = "Present"
    )

    If (!($Action)) {$Action = "Allow"}

    Write-Verbose "SET: Get Rules for the specified DisplayName [$DisplayName]"
    $FirewallRules = Get-cLegacyFWRule -DisplayName $DisplayName

    $exists = If ($FirewallRules.'Rule Name') {$true} Else {$false}

    if ($Ensure -eq "Present")
    {
        Write-Verbose "SET: We want the firewall rule to exist since Ensure is set to $Ensure"
        if ($exists)
        {
            # Ensure the existing rule matches what we want
            Write-Verbose "SET: Checking for multiple rules with the same Displayname [$DisplayName]"
            If ($FirewallRules.Count -gt 1) {
                Write-Verbose "SET: Multiple rules found. Removing all rules before creating a new rule"
                $DeleteCommand = @(netsh advfirewall firewall delete rule name=$DisplayName)
            }

            Write-Verbose "SET: Check firewall rule for valid properties"
            foreach ($FirewallRule in $FirewallRules)
            {
                Write-Verbose "SET: Check each defined parameter against the existing firewall rule [$($FirewallRule.'Rule Name')]"
                if (Test-RuleHasProperties -FirewallRule $FirewallRule `
                                           -DisplayName $DisplayName `
                                           -Enabled $Enabled `
                                           -Direction $Direction `
                                           -Profiles $Profiles `
                                           -LocalIP $LocalIP `
                                           -RemoteIP $RemoteIP `
                                           -Program $Program `
                                           -LocalPort $LocalPort `
                                           -RemotePort $RemotePort `
                                           -Protocol $Protocol `
                                           -Action $Action)
                {
                    # Do nothing, firewall rule is correct
                }
                else
                {
                    # Remove firewall rule and create new rule with correct parameters
                    Write-Verbose "SET: Removing existing firewall rule [$DisplayName] to recreate one based on desired configuration"
                    $DeleteCommand = @(netsh advfirewall firewall delete rule name=$DisplayName)

                    $AddCommand = Add-cLegacyFWRule
                    If ($AddCommand -like "Ok*") {Write-Verbose "SET: The firewall rule [$DisplayName] was added"}
                }
            }
        }
        else
        {
            # Create the rules due to '$Ensure -eq "Present"'
            Write-Verbose "SET: We want the firewall rule [$DisplayName] to exist, but it does not"

            $AddCommand = Add-cLegacyFWRule
            If ($AddCommand -like "Ok*") {Write-Verbose "SET: The firewall rule [$DisplayName] was added"}
        }
    }
    elseif ($Ensure -eq "Absent")
    {
        Write-Verbose "SET: We do not want the firewall rule to exist"
        if ($exists)
        {
            # Remove the existing rule due to '$Ensure -eq "Absent"'
            Write-Verbose "SET: We do not want the firewall rule to exist, but it does. Removing the Rule(s)"
            $DeleteCommand = @(netsh advfirewall firewall delete rule name=$DisplayName)
            If ($DeleteCommand -like "*Deleted*rule*") {Write-Verbose "SET: The firewall rule with Displayname [$Displayname] was deleted"}
        }
        else
        {
            # Do Nothing
            Write-Verbose "SET: We do not want the firewall rule to exist, and it does not"
        }
    }
}

I have used Test-RuleHasProperties in this function as well just to confirm the Firewall Rules before the creation or deletion. This was primarily during my testing for the function and can likely be cleaned out a bit.

The primary lines of this function are:

Line 91->94: These lines will remove any existing Firewall Rule with the same $DisplayName as being called, then create a new Firewall Rule using the netsh command. It will then check to ensure the new rule was created correctly by searching for 'Ok' in the netsh command's output.

Line 114->115: If ($Ensure -eq Absent) these lines will remove the Firewall Rule and then confirm the deletion was successful by searching for 'Deletedrule*' in the netsh command's output.

I haven't made this post quite as detailed as the last, primarily due to its length (and it's been a long time coming, so I wanted to get it published!). I will follow up with the last post on this DSC Resource to cover the helper functions. By then, I'm hoping a copy of this will also be available in the master branch of PowerShell.org's cNetworking Resource on github.

Until next time... automate!

iPhone GPS tracking = 'snap to road' issues

It's time for my first non-PowerShell related post. This time round its about my efforts to find an iOS app that provides relatively accurate iPhone GPS tracking - harder that you might think!

I have recently starting riding an Evolve Skateboard around which is some of the best all year round fun I reckon you can have! I highly recommend these electric longboards to anyone wanting a slightly different way of commuting or just having fun. That's for another post though...

I wanted to track my rides to see how far/fast I was travelling. I didn't want to spend huge amounts of money in buying a Garmin or Magellan GPS so I turned to my trusty iPhone GPS. It turns out once you're moving faster than approximately running pace this is harder than one might think... It seems this is to do with a Location Services class the developers can specify, CLActivityType vs CLActivityTypeFitness. From what I can gather, the Wahoo Fitness app sets this correctly whereas RunKeeper and Strava do not.

The iPhone GPS app's I've tried are RunKeeper, Strava and Wahoo Fitness and I've settled on Wahoo (with my internet connection turned off).

RunKeeper

This is where I started; I already had it installed and used to use it a lot for walking/running in the past. After my first ride with RunKeeper I checked the map uploaded to RunKeeper's website and was shocked! Apparently I wasn't travelling on that little bike path anymore, I was boarding around the road itself, even when the road was a good 5-10 metres from the path. That definitely wasn't right.

I started looking around the web for similar issues and this is a very widespread problem, especially for cyclists. It seems Apple's Location Services detects the iPhone is moving fast enough that you could be in a car so it decides to 'move' your GPS reading onto a road so the app can provide better turn-by-turn tracking. Now this isn't such a bad idea, if I was in a car using turn-by-turn navigation... but I'm not. I read some more and found that turning off the iPhone's internet connection should prevent the phone's GPS from 'snapping' to roads. I tried this with RunKeeper and it didn't help at all - so I moved on to try something else.

Strava

I loaded up Strava as that seems to be all the rage with the cyclists around here... well nope, same issue there! Even with the internet connection turned off Strava's GPS map was still showing that I was on a road more often than the bike path... next!

Wahoo Fitness

I then read about Wahoo Fitness and some reports were saying it was pretty good once your internet was turned off. So far it seems that is the case. My GPS tracks are much closer to the paths I'm actually travelling on, however if I forget to turn the internet off it is definitely just as bad as all the other apps for this.

An added bonus is Wahoo can export your activity directly into both Strava and RunKeeper so I can continue to use either (or both) of those platforms to track my total distance. Now, if only one of these platforms supported 'Long Boarding' or 'Electric Skateboarding' as an option......


So there you have it, from my experience, Wahoo Fitness with internet connectivity turned off is the best iPhone GPS app I've found for tracking fitness related activities.

NOTE: The link below is to a blog that was the primary source of information I found around the iPhone, GPS tracking and the CLActivityType/CLActivityTypeFitness

UPDATE:

So I thought Wahoo Fitness was the go to sort out my iPhone GPS issues, it turns out I was wrong. Here's what today's track showed - so quite clearly even Wahoo Fitness is snapping my track to the road when I'm going too fast. Grrrrrr

I guess I might just look at buying a cheap Garmin GPS.

PowerShell DSC: cLegacyFirewall Get-TargetResource

To follow up from my post on my Get-cLegacyFWRule function, here is the detail surrounding my cLegacyFirewall Desired State Configuration (DSC) Resource.

Next up is the cLegacyFirewall.psm1 file, which includes the three primary Functions, Get-TargetResource, Set-TargetResource and Test-TargetResource.

Get-TargetResource will do exactly that, retrieve the computer's current settings that relate to the Resource being called, in this case the Function will retrieve all Firewall Rules with the 'DisplayName' being called. The code for the resource is below.

Function Get-TargetResource
{
    [OutputType([Hashtable])]
    param
    (
        # Localized, user-facing name of the Firewall Rule being created
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$DisplayName,

        # Enable or disable the supplied configuration
        [ValidateSet("Yes", "No")]
        [string]$Enabled,

        # Direction of the connection
        [ValidateSet("In", "Out")]
        [string]$Direction,

        # Specifies one or more profiles to which the rule is assigned
        [ValidateSet("Any", "Public", "Private", "Domain")]
        [string]$Profiles,

        # Local IP used for the filter
        $LocalIP,

        # Remote IP used for the filter
        $RemoteIP,

        # Path and file name of the program for which the rule is applied
        [string]$Program,

        # Local Port used for the filter
        $LocalPort,

        # Remote Port used for the filter
        $RemotePort,

        # IP Protocol used for the filter
        $Protocol,

        # Permit or Block the supplied configuration
        [ValidateSet("Bypass", "Allow", "Block")]
        $Action,

        # Ensure the presence/absence of the resource
        [ValidateSet("Present", "Absent")]
        [String]$Ensure
    )

    Write-Verbose "GET: Get Rules for the specified DisplayName[$DisplayName]"
    $FirewallRules = Get-cLegacyFWRule -DisplayName $DisplayName

    if (!($FirewallRules.'Rule Name'))
    {
        Write-Verbose "GET: Firewall Rule does not exist"

        $ReturnValue = @{
            Ensure = "Absent"
        }

        return $ReturnValue
    }

    foreach ($FirewallRule in (($FirewallRules | Sort)[0]))
    {
        $RuleName = $FirewallRule.'Rule Name'
        Write-Verbose "GET: Firewall rule found. Adding rule [$RuleName] to return object as [Rule $i : $RuleName]" -Verbose
        $ReturnValue = @{
            DisplayName  = $FirewallRule.'Rule Name'
            Enabled      = $FirewallRule.Enabled
            Direction    = $FirewallRule.Direction
            Profiles     = $FirewallRule.Profiles
            LocalIP      = $FirewallRule.LocalIP
            RemoteIP     = $FirewallRule.RemoteIP
            Program      = $FirewallRule.Program
            LocalPort    = $FirewallRule.LocalPort
            RemotePort   = $FirewallRule.RemotePort
            Protocol     = $FirewallRule.Protocol
            Action       = $FirewallRule.Action
            Ensure       = "Present"
        }
    }

    return $ReturnValue
}

After the 'param' block which is likely fairly self explanatory, I am doing the following...

Line 51: Calling my Get-cLegacyFWRule Function to retrieve all current Firewall Rules matching the DisplayName, and placing them into the $FirewallRules variable.

Line 53: If no rules exist that match the DisplayName, return 'Ensure = "Absent"'

Line 64: Firewall rules matching the DisplayName exist, we will simply return the 'first' rule in this instance. For my usage this was sufficient. It was a while ago when I wrote this Resource and I recall having issues trying to return multiple rules in the hashtable. If anyone could provide answers on this - how to return the hashtable for something like this with multiple rules that would be of great help!

Line 84: Lastly we'll return this $ReturnValue variable.

And there you have it, my Get-TargetResource code for retrieving DSC to retrieve a firewall rule on Windows Server 2008 R2 using my Get-cLegacyFWRule function from my previous post.

Next up, I'll bring it all together with the remaining parts of the DSC resource (as the 'Set-TargetResource' and 'Test-TargetResource' are both very similar to this).

PowerShell Function: Get-LegacyFWRule

This post started out to be about a custom DSC Resource I've written (LegacyFirewall), however before getting to that I thought I'd start with a Function I'm using within that Resource. I think this is probably a more useful starting point, as I'm using PowerShell to run a dos command, and write the output into a PowerShell object. I end up using a similar Function/technique in a number of my scripts.

Regarding Desired State Configuration Resources, Microsoft with their DSC Resource Kit's have done a great thing to expand the Resources available in DSC (I think more of these should be 'shipped' and supported though), and as you'd expect these are all written to take full advantage of Windows Server 2012 and 2012 R2 and the PowerShell cmdlets available in those Operating Systems. This is all well and good, until you're forced to work with something 'out of scope'.

In my case, that is Windows Server 2008 R2. Due to vendor requirements, I have application servers that are only supported on this Operating System, and I had planned to use DSC to automate as much of the installation process as possible, from the base virtual machine, adding to domain, base config and finally the installation and configuration of the vendors application. As part of this deployment, I'm using a mix of PowerShell Modules I've written to handle specifics that require user intervention, and DSC to handle some specifics of the VM baseline, including firewall rules.

Firewall Rules, Server 2008 R2 and DSC, now there's a bit of a tricky situation! As part of the DSC Resource Kit's, Microsoft have released the xNetworking Module, which includes the xFirewall Resource, which is great! ... Except it uses the PowerShell NetFirewall cmdlets found in Windows Server 2012 and 2012 R2. To be honest that is exactly what I want Microsoft to be doing, except for this specific task when I wanted the same functionality for 2008 R2. So I went ahead and performed what is essentially a re-write of the Microsoft xFirewall DSC Resource utilizing the netsh command to handle the getting and setting of the firewall rules within my DSC Resource.

I'll split the details for my DSC Resource into a number of posts. If you're wondering about the 'c' in my function name, I use a 'c' at the start of most of my PowerShell functions/modules to indicate it is 'custom', especially useful to find these cmdlets with the Get-Command cmdlet or when using ISE to tab through cmdlets.

The primary change to Microsoft's Resource was to handle the different inputs/outputs of netsh compared to the NetFirewall cmdlets, and creating custom object handling for the netsh output so it would work nicely with PowerShell loops to test existing firewall rules against each element of the Resource. To support the gathering of firewall rules I created a function, Get-LegacyFWRule. This function is inside the DSC Resource, and is not exported.

Function Get-LegacyFWRule {
    Param ([string]$DisplayName)

    @(netsh advfirewall firewall show rule name="$DisplayName" dir=in verbose) | `
        Where {$_ -match '^([^:]+):\s*(\S.*)$' } | `
        Foreach `
            -Begin {
                $FirstRun = $true
                $HashProps = @{}
            } `
            -Process {
                If (($Matches[1] -eq 'Rule Name') -and (!($FirstRun))) {
                    New-Object -TypeName PSCustomObject -Property $HashProps
                    $HashProps = @{}
                }
                $HashProps.$($Matches[1]) = $Matches[2]
                $FirstRun = $false
            } `
            -End {
                New-Object -TypeName PSCustomObject -Property $HashProps
            }
}

And now to step through the main sections of the function:

@(netsh advfirewall firewall show rule name="$DisplayName" dir=in verbose) | `

This line simply retrieves all firewall rules matching the "$DisplayName" and pipes it into the regex command.

Where {$_ -match '^([^:]+):\s*(\S.*)$' } | `

This line pipes those firewall rules into a regex match, before piping that output further. I am by no means a regex expert, in fact I'm a rank noob. This is where I turned to trusty old Google.

This regex essentially keeps each line returned by netsh as long as there is a ":" in that line. It then separates those lines into two sections, the text before the ":" and the text after it.

For example, imagine if this were the first four lines output by netsh:

Rule Name: Excel

Enabled: Yes Direction: In The three highlighted rows will be matched by the regex, with for example on row 1, "$Matches[1]" being "Rule Name" and "$Matches[2]" being "Excel". This allows each line of the rule to be added nicely to a hashtable. For more information about the $Matches variable, see the Microsoft TechNet page on 'about_Automatic_Variables'. Essentially "$Matches[0]" is the 'whole base match', followed by "$Matches[1]" being the first 'matched content', "$Matches[2]" being the second 'matched content', etc.

This output is then piped into a ForEach using these methods:

-Begin {
    $FirstRun = $true
    $HashProps = @{}
}

The "-Begin" method is here to support the 'first' rule returned by netsh, and to create a blank hashtable used to create the first PowerShell object.

-Process {
    If (($Matches[1] -eq 'Rule Name') -and (!($FirstRun))) {
        New-Object -TypeName PSCustomObject -Property $HashProps
        $HashProps = @{}
    }
    $HashProps.$($Matches[1]) = $Matches[2]
    $FirstRun = $false
}

The "-Process" method handles the creation of the Hashtable, with it's properties and values.

The "If" statement is used to identify when the line being processed has 'Rule Name', which indicates a new firewall rule is found. When this is the case, take the existing Hashtable and create a new PSCustomObject based from that Hashtable (3rd line). Then, clear the Hashtable (4th line).

The 6th line adds each rule property found with associated value to the Hashtable.

The 7th line ensures the If statement is parsed for all rules outside of the first. The reason the first rule is different is because a Hashtable with associated firewall rule properties has not been created, so we don't want to create a PSCustomObject. Granted, technically there is a Hashtable as we created this in the '-Begin' method, but for the first rule it is empty when the '-Process' method is first run.

-End {
    New-Object -TypeName PSCustomObject -Property $HashProps
}

Finally, the '-End' method is required to ensure an object is created for the last firewall rule returned.

To see visually where each section of code is run, I simply add some Write-Host commands into the function and run it to find an existing Firewall Rule. Try this below, simply change the $DisplayName variable for a rule you have (to see this better, try and find a DisplayName that has multiple entries, (eg try 'Bonjour Service' if you have that installed). You'll see the output starts with 'Begin Method', 'Process Method', 'Setting Hashtable' and continues with the 'Process Method' and 'Setting Hashtable' until a 'New Rule Match' is found. The last entry will be the 'End Method'

See the additional 'Write-Host' lines here:

Function Get-LegacyFWRule {
    Param ([string]$DisplayName)

    @(netsh advfirewall firewall show rule name="$DisplayName" dir=in verbose) | `
        Where {$_ -match '^([^:]+):\s*(\S.*)$' } | `
        Foreach `
            -Begin {
                Write-Host 'Begin Method'
                $FirstRun = $true
                $HashProps = @{}
            } `
            -Process {
                Write-Host 'Process Method'
                If (($Matches[1] -eq 'Rule Name') -and (!($FirstRun))) {
                    Write-Host 'New Rule Match'
                    New-Object -TypeName PSCustomObject -Property $HashProps
                    $HashProps = @{}
                }
                Write-Host 'Setting Hashtable'
                $HashProps.$($Matches[1]) = $Matches[2]
                $FirstRun = $false
            } `
            -End {
                Write-Host 'End Method'
                New-Object -TypeName PSCustomObject -Property $HashProps
            }
}

Lastly, the advantage of using PowerShell objects for the output is you can use the output however you like, eg:

$Rules = Get-LegacyFWRule -DisplayName 'Bonjour Service'

# Pipe the object for Sorting or Selecting
$Rules | Sort 'Rule Name',Protocol | Select 'Rule Name',Protocol

# Count the number of rules found
$Rules.Count

Most 'wrappers' I've created to allow object creation from the output of a dos command are much simpler than this, as most commands I use only have a single 'object' as their output. I'm using similar 'wrappers' all through my scripts when I find the need, not just within DSC Resources.

If anyone can do the above in a nicer way please let me know, I'm always keen to improve my code. Hopefully this can help you figure out how to 'wrap' a dos command in PowerShell.

Good luck!

PowerShell Snippet: How to replace a string within a text file

This comes up quite frequently in the scripts I'm writing - how do I use PowerShell to replace a string within a text file. Here's a simple one-liner to do this (well, the example further down is four lines even though it can be done on a single line).

The code in its most simple form:

(Get-Content 'PathToFile').Replace('OldString','NewString') | Set-Content 'PathToFile'

Here's an example I've use to replace the "$env:COMPUTERNAME" in capitals with 'localhost' within an installed application's 'configuration.xml' file:

$InputFile = "${env:ProgramFiles(x86)}\Application\configuration.xml"
$OldString = "net.tcp://$($env:COMPUTERNAME.ToUpper()):8050/Service"
$NewString = 'net.tcp://localhost:8050/Service'
(Get-Content $InputFile).Replace($OldString,$NewString) | Set-Content $InputFile

I hope this is useful for you!

The start of my tech blog

I have been wanting to start my own blog for some time now and have never gotten round to it. A few years back I was going to start one based around my interests in home theatre's, but the hobby nature of this never made me want to do it enough. This time however the idea was to start a blog originally based around PowerShell and the knowledge I've gained using it for a few years, but this could turn out to be on anything and everything.

At my workplace I have found that I am spending a lot of time writing my own PowerShell scripts, from scratch or based off someone else's scripts, to fit the needs of my work requirements. This is obviously what most people do, but hopefully some of the content I have created can help others who might have a similar need.

I'm not too sure where to start, so lets start with a small outline of my role and provide a glimpse into the scripts and Modules I've created in PowerShell. I'm yet to upload these into github, most will require 'cleansing' first.

My official job title is Technical Specialist. Essentially I’m the primary technical resource for a small government department. I handle all things ‘powered’, from our desktops, application deployment scripts, printer configurations through to handling our SAN, basic network/VLAN requirements, Hyper-V Clusters, Disaster Recovery, Backup, AD, Group Policy, file servers, application servers, the build process involved with those and everything else in between! Over the past year or two I have really started to concentrate on utilising PowerShell to ‘automate everything!’, and surprisingly I am really enjoying it.

PowerShell Modules and Functions

In my work I have found the simplest way of accessing my scripts is to turn them into Functions within Modules. This way, I can simply provide a menu item in PowerShell ISE to import those Modules for easy use. Some of the Modules I have written are:

General Functions

This was originally written to handle desktop application deployments by providing standard functions like retrieving installed applications, creating folder trees, adding and removing files, folders and registry items through to installing and invoking executables. The main reason for these functions was to provide reusable code that included built in logging in a standard format. Hyper-V Functions

This includes a range of simple functions, such as to provide an overview of our Hyper-V Replica Health, CSV capacity status, through to a custom script to balance our virtual machine load across nodes in the cluster, and to larger scripts we use to deploy all server virtual machines that includes the automated builds with specific environmental software configurations, SQL servers and custom vendor application server environments. Yes, some of this could be handled via SCVMM and templates, however due to my workplaces size (and workload), deploying SCVMM just never happened. WSUS

This was to provide simpler handling of finding and approving WSUS updates while having the ability to ‘exclude’ updates without having to worry about those going forwards. This has turned out to be a far simpler way to not only identify those updates our systems require, but also approve them for a ‘test’ WSUS group and them merging those approvals into our ‘production’ WSUS group.

Desired State Configuration (DSC):

Since discovering DSC when it came up in one of the technical sites I’m following, I had to look further into it. Now, I am using DSC to handle specific server build requirements in a far more controlled way than a simple script. While we are a small ‘shop’ and do not have large web farms or anything of that nature (which to me is one of the primary reasons DSC was initially built for), I’m able to use a mix of scripting and DSC to give me a build process that is reusable and provides an expected outcome, over and over again.

A few of the custom DSC Resources I’ve written are:

  • cLegacyFirewall

Essentially a rewrite of Microsoft’s xNetworking ‘MSFT_xFirewall’ Resource, but using a custom PowerShell wrapper to control and use the netsh command to handle the Firewall changes. This allows me to use DSC to configure firewall rules against Windows Server 2008 R2 systems.

  • cLegacyFirewallGroup

Same as above, but to control firewall group rules (such as “Remote Desktop”, “Remote Event Log Management”, etc)

  • cDCOMPermissions

A DSC controlled way to set specific DCOM ‘Machine Access’, ‘Machine Launch’, ‘Default Access’ and ‘Default Launch’ permissions using a PowerShell wrapper for dcomperm.exe.

  • cIPv6NicBinding

To enable/disable an IPv6 binding on network interfaces

  • cEnableNetworkDTCAccess, cDisableNetworkDTCAccess

As the names suggest, enabling and disabling Network DTC Access.

Hopefully this shows some insight into my work with PowerShell, and I plan on following this post up in the future with some in depth detail on these PowerShell scripts. Unless I have input from someone about a topic they’d like, I’ll probably start with my cLegacyFirewall DSC Resource as it also covers how I have 'wrapped' netsh.exe with PowerShell, a process I'm finding to be more and more useful.