{"id":36535,"date":"2019-05-10T09:07:49","date_gmt":"2019-05-10T16:07:49","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/premier-developer\/?p=36535"},"modified":"2019-05-02T12:17:55","modified_gmt":"2019-05-02T19:17:55","slug":"building-a-better-dsc-script","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/building-a-better-dsc-script\/","title":{"rendered":"Building a better DSC script"},"content":{"rendered":"<p>App Dev Manager\u00a0<a href=\"https:\/\/www.linkedin.com\/in\/mpazicni\/\">Mark Pazicni<\/a> strives to build a better DSC script.<\/p>\n<hr \/>\n<p>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.<\/p>\n<p>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.<\/p>\n<p>Step 1: Setting up a local log directory \u2013 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.<\/p>\n<pre class=\"lang:ps decode:true \">## Variable for local Log directory\r\n[string] $LocalLogDirectory = \"C:\\Log\"\r\n##Unique Log File Name\r\n[string]$ScriptLogName = $ServiceToInstall+\"_Script_Log_\"+(Get-Date).ToString('yyyyMMddHHmmss')\r\n##Local Log Path and File Name\r\n[string] $LocalLogFileName = \"$LocalLogDirectory\\$ScriptLogName.log\"\r\nIf((Test-Path $LocalLogDirectory) -eq $false) {New-Item -Path $LocalLogDirectory -ItemType Directory}\r\nIf((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.\"}\r\nIf((Test-Path $LocalLogFileName) -eq $false) {New-Item -Path $LocalLogFileName -ItemType File | Out-Null}\r\n<\/pre>\n<p>Step 2: Setting up a Transcript \u2013 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\u2019s that are failing on machines.<\/p>\n<p>To force <strong>Write-Verbose<\/strong> cmdlet to display a status message regardless of value of the $VerbosePreferences variable, follow the <strong>Write-Verbose<\/strong> with a <strong>-Verbose<\/strong>. These status messages will not only show up in the Transcript file but also in the DSCExtensionHeader.XXX.log file. Otherwise <strong>Write-Verbose <\/strong>status message will only show up in the Transcript file.<\/p>\n<pre class=\"lang:ps decode:true \">Start-Transcript -Path $LocalLogFileName -Append -IncludeInvocationHeader\r\nWrite-Verbose (\"Starting Script: \" + (Get-Date).ToString(\"yyyyMMddHHmmss\")) -Verbose                \r\nWrite-Verbose \"Logging Parameters\"\r\nWrite-Verbose \"Local Log Directory ($LocalLogDirectory)\"\r\n<\/pre>\n<p>Step 3: Wrapping your whole script in try\u2026catch\u2026 finally block will ensure that your script running and all message are logged ensuring you can capture all status messaging in your script.<\/p>\n<pre class=\"lang:ps decode:true \">## Variable local Log directory\r\n[string] $LocalLogDirectory = \"C:\\Log\"\r\n##Script Log File Name\r\n[string]$ScriptLogName = $ServiceToInstall+\"_Script_Log_\"+(Get-Date).ToString('yyyyMMddHHmmss')\r\n##Local Log Path and File Name\r\n[string] $LocalLogFileName = \"$LocalLogDirectory\\$ScriptLogName.log\"\r\n\r\ntry \r\n{\r\n    If((Test-Path $LocalLogDirectory) -eq $false) {New-Item -Path $LocalLogDirectory -ItemType Directory}\r\n    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.\"}\r\n    If((Test-Path $LocalLogFileName) -eq $false) {New-Item -Path $LocalLogFileName -ItemType File | Out-Null}\r\n                    \r\n    Start-Transcript -Path $LocalLogFileName -Append -IncludeInvocationHeader\r\n    Write-Verbose (\"Starting Script: \" + (Get-Date).ToString(\"yyyyMMddHHmmss\")) -Verbose\r\n    \r\n    Write-Verbose \"Logging Parameters\"\r\n    Write-Verbose \"Local Log Directory ($LocalLogDirectory)\"\r\n\r\n    ## Run a bit of code here\r\n}\r\ncatch [System.SystemException] \r\n{\r\n    Write-Verbose \"An exception has occured during the installation process.\" -Verbose\r\n    Write-Verbose (\"Exception =&gt;{0}`n\" -f $_.Exception.ToString()) -Verbose\r\n}\r\ncatch\r\n{\r\n    Write-Verbose \"An error has occured during the installation process.\" -Verbose\r\n    Write-Verbose \"Message =&gt; $_`n\" -Verbose\r\n}\r\nfinally\r\n{\r\n    Write-Verbose (\"Ending Script: \" + (Get-Date).ToString(\"yyyyMMddHHmmss\")) -Verbose\r\n    Stop-Transcript\r\n}\r\n<\/pre>\n<p>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.<\/p>\n<pre class=\"lang:ps decode:true \">$localTimedWebclientCode = @\"\r\nusing System.Net;\r\n\r\npublic class TimedWebClient : WebClient\r\n{\r\n    protected override WebRequest GetWebRequest(System.Uri address)\r\n    {\r\n        WebRequest request = base.GetWebRequest(address);\r\n        if (request != null)\r\n        {\r\n            request.Timeout = 300;\r\n        }\r\n        return request;\r\n    }\r\n}\r\n\"@;\r\nAdd-Type -TypeDefinition $localTimedWebclientCode -Language CSharp\r\n## New TimedWebClient to set timeout for download of Installation File\r\n$webClient = New-Object TimedWebClient;\r\n$webClient.DownloadFile($FullUri, $InstallationFile)\r\n<\/pre>\n<p>Additional Resources:<\/p>\n<p><strong>Azure Automation State Configuration Overview &#8211; <\/strong><a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/automation\/automation-dsc-overview\">https:\/\/docs.microsoft.com\/en-us\/azure\/automation\/automation-dsc-overview<\/a><\/p>\n<p><strong>Forward Azure Automation State Configuration reporting data to Azure Monitor logs &#8211; <\/strong><a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/automation\/automation-dsc-diagnostics\">https:\/\/docs.microsoft.com\/en-us\/azure\/automation\/automation-dsc-diagnostics<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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.<\/p>\n","protected":false},"author":582,"featured_media":36537,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[80,22],"tags":[21,207,3],"class_list":["post-36535","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net","category-devops","tag-devops","tag-dsc","tag-team"],"acf":[],"blog_post_summary":"<p>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.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36535","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=36535"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36535\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/36537"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=36535"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=36535"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=36535"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}