Building a better DSC script

Developer

App Dev Manager Mark Pazicni strives to build a better DSC script.


For those people that want to build a better DSC script, one that checks for most conditions and wants to keep their custom scripts from failing with no error handling, check out these recommendations.

When writing a custom script in DSC, it comes down to PowerShell and how much you can ensure that you check for each situation in your script. You also need a way to verbosely write out what is happening in that script.

Step 1: Setting up a local log directory – If the log directory does not exist, create one, but if ones does exists, then you need ensure you have access to the directory. With a quick check of the ACL you can ensure that your script does not fail to process. Once you know the directory exists and you have access creating the log file is easy.

## Variable for local Log directory
[string] $LocalLogDirectory = "C:\Log"
##Unique Log File Name
[string]$ScriptLogName = $ServiceToInstall+"_Script_Log_"+(Get-Date).ToString('yyyyMMddHHmmss')
##Local Log Path and File Name
[string] $LocalLogFileName = "$LocalLogDirectory\$ScriptLogName.log"
If((Test-Path $LocalLogDirectory) -eq $false) {New-Item -Path $LocalLogDirectory -ItemType Directory}
If((Get-Acl $LocalLogDirectory).GetAccessRules($true,$false,[System.Security.Principal.NTAccount]) | Where-Object { $_.FileSystemRights -eq "Write" -and $_.AccessControlType -eq "Deny" }) {throw "Write Access to path ($LocalLogDirectory) Denied."}
If((Test-Path $LocalLogFileName) -eq $false) {New-Item -Path $LocalLogFileName -ItemType File | Out-Null}

Step 2: Setting up a Transcript – this option records several system details as well as verbose message that written out of any command or comments made in code. This helps with debugging DSC’s that are failing on machines.

To force Write-Verbose cmdlet to display a status message regardless of value of the $VerbosePreferences variable, follow the Write-Verbose with a -Verbose. These status messages will not only show up in the Transcript file but also in the DSCExtensionHeader.XXX.log file. Otherwise Write-Verbose status message will only show up in the Transcript file.

Start-Transcript -Path $LocalLogFileName -Append -IncludeInvocationHeader
Write-Verbose ("Starting Script: " + (Get-Date).ToString("yyyyMMddHHmmss")) -Verbose                
Write-Verbose "Logging Parameters"
Write-Verbose "Local Log Directory ($LocalLogDirectory)"

Step 3: Wrapping your whole script in try…catch… finally block will ensure that your script running and all message are logged ensuring you can capture all status messaging in your script.

## Variable local Log directory
[string] $LocalLogDirectory = "C:\Log"
##Script Log File Name
[string]$ScriptLogName = $ServiceToInstall+"_Script_Log_"+(Get-Date).ToString('yyyyMMddHHmmss')
##Local Log Path and File Name
[string] $LocalLogFileName = "$LocalLogDirectory\$ScriptLogName.log"

try 
{
    If((Test-Path $LocalLogDirectory) -eq $false) {New-Item -Path $LocalLogDirectory -ItemType Directory}
    If((Get-Acl $LocalLogDirectory).GetAccessRules($true,$false,[System.Security.Principal.NTAccount]) | Where-Object { $_.FileSystemRights -eq "Write" -and $_.AccessControlType -eq "Deny" }) {throw "Write Access to path ($LocalLogDirectory) Denied."}
    If((Test-Path $LocalLogFileName) -eq $false) {New-Item -Path $LocalLogFileName -ItemType File | Out-Null}
                    
    Start-Transcript -Path $LocalLogFileName -Append -IncludeInvocationHeader
    Write-Verbose ("Starting Script: " + (Get-Date).ToString("yyyyMMddHHmmss")) -Verbose
    
    Write-Verbose "Logging Parameters"
    Write-Verbose "Local Log Directory ($LocalLogDirectory)"

    ## Run a bit of code here
}
catch [System.SystemException] 
{
    Write-Verbose "An exception has occured during the installation process." -Verbose
    Write-Verbose ("Exception =>{0}`n" -f $_.Exception.ToString()) -Verbose
}
catch
{
    Write-Verbose "An error has occured during the installation process." -Verbose
    Write-Verbose "Message => $_`n" -Verbose
}
finally
{
    Write-Verbose ("Ending Script: " + (Get-Date).ToString("yyyyMMddHHmmss")) -Verbose
    Stop-Transcript
}

Lastly one other useful trick in writing better DSC scripts to create custom .NET code snippets that you can add to this script when necessary to do simple functions done in .NET. In this example we are overriding the base web client to add a timeout value so that if our download request does not complete with a specific time the script can be abort an easy find exception can be logged.

$localTimedWebclientCode = @"
using System.Net;

public class TimedWebClient : WebClient
{
    protected override WebRequest GetWebRequest(System.Uri address)
    {
        WebRequest request = base.GetWebRequest(address);
        if (request != null)
        {
            request.Timeout = 300;
        }
        return request;
    }
}
"@;
Add-Type -TypeDefinition $localTimedWebclientCode -Language CSharp
## New TimedWebClient to set timeout for download of Installation File
$webClient = New-Object TimedWebClient;
$webClient.DownloadFile($FullUri, $InstallationFile)

Additional Resources:

Azure Automation State Configuration Overview – https://docs.microsoft.com/en-us/azure/automation/automation-dsc-overview

Forward Azure Automation State Configuration reporting data to Azure Monitor logs – https://docs.microsoft.com/en-us/azure/automation/automation-dsc-diagnostics

0 comments

Comments are closed. Login to edit/delete your existing comments