Thursday, 23 November 2017

Learn PowerShell DSC - Part 7

Introduction

In this part, we’ll go ahead and create our own DSC Resource. Why are we doing this? Well, when you start using DSC, you’ll find that you need to do something but cannot find a DSC Resource for it.

Other parts in this series:

Create a new DSC Resource

We’re going to create a simple resource which ensures a folder is present or absent. There’s already a DSC Resource for this but we’re more focussed on the process of creating a DSC resource. An overview of the steps is below:

  1. Install the DSC Resource Designer Module
  2. Create the DSC Resource properties
  3. Create the DSC Resource files
  4. Create the module manifest
  5. Add code to the DSC Resource
  6. Copy the module to the PowerShell module directory
  7. Test the DSC Resource

Let’s get to it - follow the steps below.

1) Install the DSC Resource Designer module

This module includes the cmdlets we’ll use to create a new DSC resource. These cmdlets include:

  • Import-xDscSchema
  • New-xDscResource
  • New-xDscResourceProperty
  • Test-xDscResource
  • Test-xDscSchema
  • Update-xDscResource

Install it by running the command below:

Install-Module xDscResourceDesigner -Force

2) Create the DSC Resource properties

These DSC Resource properties relate to what can be set when using the DSC Resource as part of a configuration. Our DSC Resource will be quite simple so in this case, we only need two properties:

  • Path - this is the path to the folder we will ensure is absent or present
  • Ensure - this is what we will do to the folder. It will only be able to be set to Present or Absent.

$Path=New-xDscResourceProperty -Name Path -Type string -Attribute Key
$Ensure=New-xDscResourceProperty -Name Ensure -Type string -Attribute Write -ValidateSet Present, Absent

3) Create the DSC Resource files

We’ll use the New-xDscResource cmdlet to create the file and folder structure for our new DSC Resource which we’ll call DemoFolder. We’ll call our new module DemoModule so we’ll be saving our new DSC Resource in a DemoModule folder:

New-xDscResource -Name DemoFolder -Property $Path, $Ensure -Path 'C:\scripts\DemoModule'

This creates a number of files and folder structure in C:\Scripts. We’ll go through this later on.

4) Create the module manifest

As when creating a standard PowerShell module, we need to create a module manifest which includes information about the module. If you want more information on how to create a PowerShell module, see here.

Let’s create our module manifest. We’re going to set a few options here:

  • Author: MG
  • CompanyName: MG Enterprises
  • Description: Demo Module

New-ModuleManifest `
-Path 'C:\Scripts\DemoModule\DemoModule.psd1' `
-Guid ([GUID]::NewGuid()) `
-ModuleVersion 1.0 `
-Author MG `
-CompanyName 'MG Enterprises' `
-Description 'Demo Module'

5) Add code to the DSC Resource

We should now have the below folder structure and files:

image

  • DemoModule.psd1 - this is the module manifest we created in step 4
  • DemoFolder.psm1 - this is the PowerShell module file which will hold our DSC Resource code
  • DemoFolder.schema.mof - this is the MOF schema which contains information about the DSC Resource

When we look in the .psm1 file for a particular DSC Resource we've created, we'll see that the DSC resource designer created three empty functions with some parameters which match the properties you created with New-xDscResourceProperty. These are:

  • Test-TargetResource - This function checks if the target machine configuration matches the DSC configuration. The function needs to provide a Boolean output (true or false) and it should be fast and lightweight as it will potentially run regularly.
  • Set-TargetResource - This is what performs the configuration change if the Test-TargetResource comes back false - i.e. in our case it either creates or deletes the specified folder.
  • Get-TargetResource - This must return a hash table of the current configuration. It's not used when setting or checking the configuration. It's only used when we use Get-DscConfiguration i.e. when troubleshooting.

Our 'empty' DemoFolder.psm1 file is below:

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <# $returnValue = @{ Path = [System.String] Ensure = [System.String] } $returnValue #>
}


function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    #Include this line if the resource requires a system reboot.
    #$global:DSCMachineStatus = 1

}


function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <# $result = [System.Boolean] $result #>
}

Export-ModuleMember -Function *-TargetResource


Let’s add some code to our DemoFolder.psm1 file. I’m not going to go through each line of code as this will really take some time and this post is all about creating the DSC resource.

Below you can see the completed file where I’ve written out the code.


function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path
    )

    Write-Verbose "Checking if folder is present" 

    if(Get-Item $Path -ErrorAction SilentlyContinue -ErrorVariable a)
        {
            $Ensure="Present”
            if($a.Exception){Write-Debug $a.Exception} 
        }
    else
        {
            $Ensure="Absent”
            if($a.Exception){Write-Debug $a.Exception}
        }

    # Create a hashtaable which has path, present and ensure
    $returnValue = @{
    Path = [System.String]$Path
    Ensure = [System.String]$Ensure
    }

    # Output the hashtable - this is used for troubleshooting
    $returnValue
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    # If ensure is true, create the folder and if false then delete it and its contents
    if($Ensure -eq 'Present')
        {
            Write-Verbose "Creating the folder"
            New-Item -ItemType Directory -Path $Path -ErrorAction SilentlyContinue -ErrorVariable a | Out-Null
            if($a.Exception){Write-Debug $a.Exception}

        }
    else
        {
            Write-Verbose "Removing the folder"
            Remove-Item $Path -Recurse -Force -ErrorAction SilentlyContinue -ErrorVariable a
            if($a.Exception){Write-Debug $a.Exception}
        }
   
    #Include this line if the resource requires a system reboot.
    #$global:DSCMachineStatus = 1

}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure
    )

    # If ensure is true and the folder exists, return true. If folder doesn't exist then return false.
    # If ensure is false and folder exists then return false. If folder doens't exist then return true.
    if($Ensure -eq 'Present')
        {
            Write-Verbose "Checking if the folder exists"
            if(Get-Item $Path -ErrorAction SilentlyContinue -ErrorVariable a)
                {
                    $Result = $true
                }
            else
                {
                    $Result = $false
                }
            if($a.Exception){Write-Debug $a.Exception}
        }
    else
        {
            Write-Verbose "Checking that the folder does not exist"
            if(Get-Item $Path -ErrorAction SilentlyContinue -ErrorVariable a)
                {
                    $Result = $false
                }
            else
                {
                    $Result = $true
                }
            if($a.Exception){Write-Debug $a.Exception}
        }
  
    # Write the boolean output - if false then DSC knows to then run the Set-DSCTargetResource function
    $result
}

Export-ModuleMember -Function *-TargetResource

6) Copy the module to the PowerShell module directory

We now need to copy our module directory over to the PowerShell module directory:

Copy-Item C:\scripts\DemoModule "C:\Program Files\WindowsPowerShell\Modules\DemoModule" -Recurse

We can now confirm that our new module and DSC Resource is visible to PowerShell:

Get-DscResource DemoFolder

image

7) Test the DSC Resource

So, we’re done creating our DSC Resource and are ready to create a DSC configuration to test it out.

Our simple test configuration is below - all it does is ensure that the C:\TestFolder is present on localhost. If you need a refresher on how to write configurations, then see part 1.

configuration DemoCreateFolder {
    param
        (
            [Parameter(Mandatory=$true)]
            [string[]]$ComputerName
        )      
Import-DscResource -ModuleName DemoModule

    Node $ComputerName
     {
            DemoFolder TestFolder
                {
                   Ensure = "Present"
                   Path = "C:\TestFolder"
                }
        }
}

We then create the DSC Configuration document (MOF file) as normal:

DemoCreateFolder -ComputerName localhost -OutputPath C:\DSC\Configs

Once done, let’s go ahead and push our DSC configuration:

Start-DscConfiguration -Path C:\DSC\Configs -Wait -Verbose

image

We can then confirm our target machine is in the desired state and that our test folder exists:

Test-DscConfiguration -Path C:\DSC\Configs
Get-Item C:\TestFolder

image

So, there we have it - we now know how to create DSC Resources. Next time we’ll go through composite resources and how we can save time.

Happy scripting!

Monday, 13 November 2017

Learn PowerShell DSC - Part 6

Introduction

In Part 5, started looking at DSC Pull using an SMB share. If you need a refresher, click here. In this part, we’ll run through the setup of HTTP/HTTPS pull which is a little more complex but makes life a little easier especially as it requires fewer firewall ports open and is more secure.

Other parts in this series:

What is DSC HTTP/HTTPS Pull?

HTTP/HTTPS pull allows you to store your DSC configurations (.MOF) on a web server which the target machines can connect to, download and apply their configurations. They do this over the standard ports: TCP port 80 for HTTP and TCP port 443 for HTTPS.

The setup steps we will run through are:

  1. Install the required DSC module on the DSC pull server
  2. Install a certificate on the DSC pull server
  3. Create a DSC configuration to deploy the DSC pull server
  4. Create a MOF file
  5. Deploy the DSC configuration to the DSC pull server

The steps are almost the same whether you want to set up an HTTP or an HTTPS pull server but there are some differences.

Set up a DSC HTTP/HTTPS pull server

1) Install the required DSC module on the DSC pull server

We need to use some of the DSC Resources which are available in the xPSDesiredStateConfiguration DSC module which is not included in Windows. To install this module, we run the command:

Install-Module xPSDesiredStateConfiguration

2) Install a certificate on the DSC pull server

The next step is to install a certificate on the HTTPS pull server. If you don’t need to have encrypted traffic and therefore don’t need to use HTTPS then you can skip this step. The certificate needs to have the correct Subject Name. Now, adding the certificate

In my case, my DSC pull server is called contchidsc01.contoso.com so this is what I need on my certificate. I’ll just confirm that contchidsc01 has a certificate with the correct subject:

Enter-PSSession contchidsc01
Get-ChildItem Cert:\LocalMachine\My\ | fl Thumbprint,Subject

image

We also need to note down the certificate thumbprint as we’ll need this in the next step. In my case it’s F8E20068359E75922F3EC35F58282C348D4511CF.

3) Create a DSC configuration to deploy the DSC pull server

The most interesting part! Deploying a DSC pull server requires a number of steps and so we’ll use DSC to configure the DSC pull server. The steps are below:

The full DSC configuration we need is below:

configuration HTTPSPullServer
    {
        Param (
            [Parameter(Mandatory = $true)]
            [string] $ComputerName,
            [ValidateNotNullOrEmpty()] 
            [string] $certificateThumbPrint,
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string] $RegistrationKey
        )

        Import-DSCResource -ModuleName xPSDesiredStateConfiguration
        Import-DSCResource -ModuleName PSDesiredStateConfiguration

        Node $ComputerName
            {
                WindowsFeature DSCServiceFeature
                    {
                        Ensure = "Present"
                        Name   = "DSC-Service"
                    }

                WindowsFeature IISConsole 
                    {
                        Ensure = "Present"
                        Name   = "Web-Mgmt-Console"
                    }

                xDscWebService PSDSCPullServer
                    {
                        Ensure                  = "Present"
                        EndpointName            = "PSDSCPullServer"
                        Port                    =  443
                        PhysicalPath            = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer"
                        CertificateThumbPrint   =  $certificateThumbPrint
                        ModulePath              = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules"
                        ConfigurationPath       = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
                        State                   = "Started"
                        UseSecurityBestPractices=  $false
                        DependsOn               = "[WindowsFeature]DSCServiceFeature"
                    }

                File RegistrationKeyFile
                    {
                        Ensure          = 'Present'
                        Type            = 'File'
                        DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt"
                        Contents        = $RegistrationKey
                    }
            }
    }

Let’s go through what this actually does.

This first part collects parameters for the ComputerName, certificateThumbPrint and the RegistrationKey when we run the configuration to create a MOF file:

        Param (
            [Parameter(Mandatory = $true)]
            [string] $ComputerName,
            [ValidateNotNullOrEmpty()] 
            [string] $certificateThumbPrint,
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string] $RegistrationKey
        )

  • ComputerName = The name of the target machine which we will configure to be a DSC HTTP/HTTPS pull user
  • certificateThumbprint = The thumbprint of the certificate which we installed on the DSC pull server
  • RegistrationKey = A key which target machines will use to do the initial registration with the pull server. After this initial registration, the target machine will generate a self-signed certificate which will be used to authenticate with the pull server.

This next part installs the DSC Service and IIS Management Console Windows features we need installed on our pull server:

                WindowsFeature DSCServiceFeature
                    {
                        Ensure = "Present"
                        Name   = "DSC-Service"
                    }

                WindowsFeature IISConsole 
                    {
                        Ensure = "Present"
                        Name   = "Web-Mgmt-Console"
                    }

The next part configures the DSC Web Service so that it works as a pull server. We need to specify a number of parameters here:

  • Port = Port number to use for the pull server.
  • PhysicalPath = Path on disk for the virtual directory that will be set up in IIS.
  • CertificateThumbprint = The certificate thumbprint for the certificate we installed on the pull server. As we have a parameter for this, we’ll set this to $certificateThumbPrint in the configuration. If you don’t want to use HTTPS then you can set this value to “AllowUnencryptedTraffic” and the pull server will use HTTP.
  • ModulePath = The path where DSC modules will be stored. The target machines can download any required modules if they don’t already have these installed.
  • ConfigurationPath = The path where DSC configurations will be stored.

                xDscWebService PSDSCPullServer
                    {
                        Ensure                  = "Present"
                        EndpointName            = "PSDSCPullServer"
                        Port                    =  443
                        PhysicalPath            = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer"
                        CertificateThumbPrint   =  $certificateThumbPrint
                        ModulePath              = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules"
                        ConfigurationPath       = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
                        State                   = "Started"
                        UseSecurityBestPractices=  $false
                        DependsOn               = "[WindowsFeature]DSCServiceFeature"
                    }

The final part of the configuration is to store the registration key in a text file. We use the File DSC Resource to create a new text file and set the contents to $RegistrationKey

                File RegistrationKeyFile
                    {
                        Ensure          = 'Present'
                        Type            = 'File'
                        DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt"
                        Contents        = $RegistrationKey
                    }

4) Generate the MOF file

To generate our MOF file, we need to specify our parameters:

  • ComputerName = contchidsc01
  • certificateThumbprint =  F8E20068359E75922F3EC35F58282C348D4511CF 
  • RegistrationKey =  (New-Guid).Guid

We then call our configuration, specify the output path and the parameters:

HTTPSPullServer -OutputPath C:\DSC\HTTPS -ComputerName contchidsc01 `
-certificateThumbPrint F8E20068359E75922F3EC35F58282C348D4511CF -RegistrationKey (New-Guid).Guid

5) Deploy the DSC configuration to the DSC pull server

The final step is to deploy the DSC configuration to the pull server using Start-DscConfiguration:

Start-DscConfiguration -Path C:\DSC\HTTPS -Verbose -Wait

If we use the -Verbose and -Wait parameters, we can see detailed output as below:

image

image

6) Test the configuration

Before moving on, let’s just test our configuration. We can do this using Test-DscConfiguration and we can see that contchidsc01 is in the desired state:

Test-DscConfiguration -Path C:\DSC\HTTPS

image

We can also see the PSDSCPullServer virtual directory in IIS:

image

….and we can confirm that our Registration Key was saved to the file we specified:

image

We’ll move on to configuring a target machine to use the new pull server.

Configure a target machine to use our DSC HTTPS pull server

We need to configure our target machine Local Configuration Manager (LCM) with the DSC pull server URL and the registration key so it can complete the initial registration.

We’ll be configuring our target machine contchich01. In this configuration, we’re specifying the settings below:

  • ConfigurationID: This is the ID of the LCM on the target machine and is used to find the correct configuration to apply as there may be many configurations for other machines on the same pull server
  • RefreshMode: This sets our LCM to pull instead of push which is the default
  • ConfigurationRepositoryWeb ServerURL: This is the URL of the pull server
  • ReportServerWeb ServerURL: This is the URL of the pull server
  • RegistrationKey: We specify this for both the report server and the configuration repository so the target machine can register against the pull server

[DSCLocalConfigurationManager()]
configuration ConfigurePullClient
{
     Param (
        [Parameter(Mandatory = $true)]
        [string] $ComputerName,
        [Parameter(Mandatory = $true)]
        [string] $RegistrationKey,
        [Parameter(Mandatory = $true)] 
        [string] $GUID
     )

    Node $ComputerName
    {
        Settings
        {
            RefreshMode          = 'Pull'
            ConfigurationID = $guid
        }

        ConfigurationRepositoryWeb CONTOSO-PullSrv 
        {
            ServerURL          = 'https://contchidsc01.contoso.com/PSDSCPullServer.svc/'
            RegistrationKey    = $RegistrationKey
        }  

        ReportServerWeb CONTOSO-PullSrv 
        {
            ServerURL       = 'https://contchidsc01.contoso.com/PSDSCPullServer.svc/'
            RegistrationKey =  $RegistrationKey
        }
    }
}

Once we have the LCM configuration, we need to push this out specifying the RegistrationKey, ComputerName and a GUID for the ConfigurationID:

$RegistrationKey = Get-Content '\\contchidsc01\c$\Program Files\WindowsPowerShell\DscService\RegistrationKeys.txt'

$ComputerName = "contchich01"
$GUID = (New-Guid).Guid
ConfigurePullClient -ComputerName $ComputerName -RegistrationKey $RegistrationKey -OutputPath C:\DSC\Configs -GUID $GUID

Next, we need to set the LCM using Set-DscLocalConfigurationManager:

Set-DscLocalConfigurationManager -ComputerName $ComputerName -Path C:\DSC\Configs -Verbose

image

We then run these commands to confirm the configuration:

$Settings = Get-DscLocalConfigurationManager -CimSession $ComputerName 
$Settings | fl PSComputerName,RefreshMode,RefreshFrequencyMins,RebootNodeIfNeeded,ConfigurationID
$Settings.ConfigurationDownloadManagers[0]

image

As you see above, we have the LCM set to pull and the ServerURL set to our new pull server.

Create a DSC configuration for an HTTPS pull target machine

This is basically the same as creating a normal DSC configuration but instead of naming the mof file according to our target machine i.e. contchich01.mof, we need to name it using the ConfigurationID of the LCM. The simple configuration is below and this creates a test file on the C drive called testfile.txt:

Configuration TestDSCPull {
   
    Param (
    [Parameter(Mandatory=$true)]
    [string]$ComputerName
    )
   
    Import-DscResource -ModuleName PSDesiredStateConfiguration

    Node $ComputerName {
   
        File TestFile {

            DestinationPath = 'C:\testfile.txt'
            Type = 'File'
            Ensure = 'Present'
            Contents = 'Test file contents'
            }
        }

}

Create your MOF file as normal:

$ComputerName = "contchich01"
TestDSCPull -ComputerName $ComputerName -OutputPath C:\DSC\Configs

In the next section, we get the ConfigurationID from the target machine and then copy the MOF file over to the configuration repository on the DSC pull server and rename it to <guid>.mof:

$guid=(Get-DscLocalConfigurationManager -CimSession $ComputerName).ConfigurationID
$DestinationFile = '\\contchidsc01\c$\Program Files\WindowsPowerShell\DscService\Configuration\' + $guid + '.mof'
copy C:\DSC\Configs\$ComputerName.mof $DestinationFile -Force

We also need a checksum file so create one using New-DscChecksum (use -Force to overwrite a checksum if there is already one there):

New-DscChecksum $DestinationFile -Force

To get the machine to pull the configuration, we then use Update-DscConfiguration:

Update-DscConfiguration -ComputerName $ComputerName -Wait -Verbose

image

…..and there we have it, the target machine pulled the configuration and we can confirm the test file exists and that the contents are correct:

image

Conclusion

If you’ve made it this far then take a break - you deserve it! It takes a little bit of time to get it all set up but once you’re done, all your servers can be configured to automatically pull their configuration from the pull server and you have a central repository for your configurations so all you have to do is create configurations.