Desired State Configuration: Cannot bind argument to parameter ‘ResourceKey’ because it is an empty string

Applies To: PowerShell Version: 5.0.10240.16384, xComputerManagement 1.2.2, 1.3.0

I was working on a DSC Configuration to build a Hyper-V host, when I encountered the error shown below.

So, that’s a pretty awesome error, and not one that I can say that I have seen before. I did A LOT of troubleshooting to figure out what was going on (which in the interests of time and space I am going to skip over) and we will get right to the good stuff.

My Configuration had some string parameters that were not assigned any values in the Configuration, or when I was creating the .mof files. Let’s pretend the Configuration looks like the one below. If you just copy and paste and run this, it is going to fail with the error above.

If you remove the section for xComputerManagement, it will work just fine. Also, if you hard code a value into the parameter $ComputerName in the Configuration, or pass it in when you build the Configuration (I have shown both ways below), it will also not error and will work the way you would expect it to.

So, the next question is, why is this? Thankfully, the answer is in the error message once you figure it out. The Name parameter of the DSC resource is the Key value of the resource, and as such it cannot be an empty string (which completely makes sense). It doesn’t throw the error for the File resource, because it’s key value is DestinationPath, which in my example I have coded in as an actual value. How can you tell what the Key property of a DSC Resource is? Look at the example below! Key properties are Mandatory parameters, and as such are shown without being enclosed in square brackets. You can also open up and look at the schema.mof file for the resource which will show you that information as well.

To further illustrate the point, if I run the Configuration below, it is also going to error out.

Unexpected Token ‘}’ in Expression or Statement when using $AllNodes in a Desired State Configuration

I was working on testing a custom DSC Resource when I came across a very strange issue. Whenever I tried to build a Configuration like the one below, it failed.

First Configuration:
$ConfigData =@{
    AllNodes = @(
        @{
            NodeName = '*'
            PSDSCAllowPlainTextPassword = $True
          }
    )

}

Configuration TestConfig{

    Import-DscResource -ModuleName @{ModuleName="xComputerManagement";ModuleVersion="1.3.0"}

    Node $AllNodes.Nodename{

        xComputer Test{
            Name = "MYPC"
        }

    }

}  

Under the very last curly brace there was a red squiggly line. When I tried to build the Configuration it failed with the error:

Unexpected token '}' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedToken

It took me nearly an hour and a half to figure out what was causing the issue. Here is the working Configuration. Can you spot the difference?

$ConfigData =@{
    AllNodes = @(
        @{
            NodeName = '*'
            PSDSCAllowPlainTextPassword = $True
          }
    )

}

Configuration TestConfig{

    Import-DscResource -ModuleName @{ModuleName="xComputerManagement";ModuleVersion="1.3.0"}

    Node $AllNodes.NodeName {

        xComputer Test{
            Name = "MYPC"
        }

    }

}  

If you can’t spot the difference, the only difference is that in the second example, there is a space between $AllNodes.NodeName and the curly brace beginning the Node block. As you can imagine I have a serious problem with this, and believe this to be a bug. You will notice that I don’t need a space between the name of the Configuration and the curly brace, so why should I need a space after $AllNodes.NodeName? Also, in the next example, if I change $AllNodes.NodeName to Node MYPC, it doesn’t require a space between the node name and the curly brace either.

Configuration TestConfig{

    Import-DscResource -ModuleName @{ModuleName="xComputerManagement";ModuleVersion="1.3.0"}

    Node MYPC{

        xComputer Test{
            Name = "MYPC"
        }

    }

}  

All of this testing was done on Windows 10 Enterprise Preview edition with PowerShell Version 5.0.10158.0 . If you also believe this is a bug and should be fixed, vote up the issue on connect here.

Creating a Class based DSC Resource using PowerShell

Scenario: I would like to think I am fairly competent at creating DSC Resources using .MOF files. But with the upcoming release of Windows Management Framework 5.0 we can now write DSC Resources using Classes. I have never done anything with a Class. I am going to attempt to figure out how to write a simple Class based DSC Resource.

I am going to start with this TechNet Article, because it was the first thing that came up when I Googled “Create Class based DSC Resource”.

I am going to create a DSC Resource called MyTestClassResource. The first thing I need to do is create the appropriate folder structure, which I do by running the following two commands:

New-Item -ItemType Directory -Path 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources'
New-Item -ItemType Directory -Path 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource'

Next, I need to create the Module Manifest.

$Path = 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource'
New-ModuleManifest -Path $Path\MyTestClassResource.psd1 -Author 'Jacob Benson' -ModuleVersion 1.0 -Description 'Module for Testing Class Based DSC Resources'

The .PSM1 file is where I define and create my Class based resource.

New-Item -ItemType File -Path $Path\MyTestClassResource.psm1
PSEdit -filenames 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource\MyTestClassResource.psm1'

Now, let’s get to creating this Resource. I am going to go super simple here and create a Resource that will just ensure that a Folder exists. Yes, I know the File DSC Resource already does this, that’s not the point :). After fiddling around for a little bit here is what my Class DSC Resource “Skeleton” looks like.

enum Ensure 
{ 
    Absent 
    Present 
}

[DscResource()]
class MyTestClassResource{

    [DscProperty(Key)]
    [string]$Path

    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure

    [DscProperty(Mandatory)]
    [ValidateSet("Directory","File")]
    [string]$ItemType

    #Replaces Get-TargetResource
    [MyTestClassResource] Get() 
    {

    }

    #Replaces Test-TargetResource
    [bool] Test()
    {

    }

    #Replaces Set-TargetResource
    [void] Set()
    {

    }


}

The rest of this should be pretty straightforward. Right? The first thing I try to do in the Get section is to test and see if the path exists.

$Item = Test-Path $Path

This doesn’t work however because “Variable is not assigned in the method”, whatever the hell that means. Looking at the article it uses a $This object (I have no idea if that’s even the right word) with the variables to do things, so let’s try a different tactic.

$Item = Test-Path $This.Path

And that works fine, so clearly $This is some special thing I need to be using going forward. This should be fun :). Now that I got that working, the rest of this Method was pretty straight forward.

    #Replaces Get-TargetResource
    [MyTestClassResource] Get() 
    {
        $Item = Test-Path $This.Path

        If($Item -eq $True)
        {
            $This.Ensure = [Ensure]::Present
        }
        Else
        {
            $This.Ensure = [Ensure]::Absent
        }

        Return $This

    }

After that, the Test Method is pretty easy as well. However, I have no idea what is going on with the whole Return -not $Item part, I am just following along from the example and hoping that it works.

    #Replaces Test-TargetResource
    [bool] Test()
    {
        $Item = Test-Path $This.Path

        If($This.Ensure -eq [Ensure]::Present)
        {
            Return $Item
        }
        Else
        {
            Return -not $Item
        }

    }

Onto the Set Method. One thing I noticed when creating this is that I don’t need an Else block with an If statement, which is nice.

    #Replaces Set-TargetResource
    [void] Set()
    {
        $Item = Test-Path $This.Path
        If($This.Ensure -eq [Ensure]::Present)
        {
            If(-not $Item)
            {
                Write-Verbose "Creating Folder"
                New-Item -ItemType Directory -Path $This.Path
            }
        }
        #If [Ensure]::Absent
        Else
        {
            If($Item)
            {
                Write-Verbose "File exists and should be absent.  Deleting file"
                Remove-Item -Path $This.Path
            }
        }

    }

With that done I need to re-create the Module Manifest that I made earlier with some important information.

$Path = 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource'
New-ModuleManifest -Path $Path\MyTestClassResource.psd1 -Author 'Jacob Benson' -ModuleVersion 1.0 -Description 'Module for Testing Class Based DSC Resources' -RootModule 'MyTestClassResource.psm1' -DscResourcesToExport *

At this point while trying to run Get-DSCResource I realized that my folder structure I created at the beginning was not correct, because I was used to the way I had been doing it when working with .MOF Files. I actually need only this:

$Path = 'C:\Program Files\WindowsPowerShell\Modules\'
New-Item $Path\MyTestClassResource -ItemType Directory

And then I moved the .psm1 and .psd1 files into that folder. No extra sub-folder required. This is a win in my book.

Now, when I run Get-Module -ListAvailable, the MyTestClassResource Module is listed. However, when I run Get-DSCResource -Module MyTestClassResource I get a lot of nothing. Weird. Next I try to Import-Module -Name MyTestClassResource and I get this giant bundle of joy.

$> Import-Module -Name MyTestClassResource -Verbose
VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\MyTestClassResource\MyTestClassResource.psd1'.
Import-Module : The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)
At line:1 char:1
+ Import-Module -Name MyTestClassResource -Verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Import-Module], FileLoadException
    + FullyQualifiedErrorId : System.IO.FileLoadException,Microsoft.PowerShell.Commands.ImportModuleCommand

Uhhh…What? Thinking quickly I decide that it’s probably not going to work to name everything exactly the same. I rename the MyTestClassResource folder to MyTestClassResources and leave everything else the same. And well, I will spare all the error text but that didn’t work at all either. I am not sure how much time I spent trying to figure out what the hell I was doing wrong, but to say it was an exercise in frustration is a massive understatement. No matter what I did I couldn’t get it to recognize a valid Module, let alone DSC Resource. I lost track of all the troubleshooting that I did but here is what my folder structure looks like now that it is working. Note that I renamed my Class based Resource from MyTestClassResource to MyFolderResource.
ClassTest

$> Get-DscResource -Module MyFolderResource

ImplementedAs   Name                      ModuleName                     Version    Properties                                        
-------------   ----                      ----------                     -------    ----------                                        
PowerShell      MyFolderResource          MyFolderResource               1.0        {Ensure, Path, DependsOn, PsDscRunAsCredential}   



$> Get-DscResource -Module MyFolderResource -Syntax
MyFolderResource [String] #ResourceName
{
    Ensure = [string]{ Absent | Present }
    Path = [string]
    [DependsOn = [string[]]]
    [PsDscRunAsCredential = [PSCredential]]
}

Now, let’s test a Configuration!

Configuration TestClass
{
    Import-DSCResource -ModuleName MyFolderResource
    MyFolderResource Test
    {
        Ensure = "Present"
        Path =   "C:\Scripts\Blah"
    }


}

TestClass -OutputPath C:\Scripts\TestClass
Start-DSCConfiguration -Wait -Force -Verbose -Path C:\Scripts\TestClass

$> Start-DSCConfiguration -Wait -Force -Verbose -Path C:\Scripts\TestClass
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windo
ws/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer VALHALLA with user sid S-1-5-21-3449200860-990092898-4058043841-1001.
VERBOSE: [VALHALLA]: LCM:  [ Start  Set      ]
VERBOSE: [VALHALLA]: LCM:  [ Start  Resource ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]: LCM:  [ Start  Test     ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]: LCM:  [ End    Test     ]  [[MyFolderResource]Test]  in 0.2180 seconds.
VERBOSE: [VALHALLA]: LCM:  [ Start  Set      ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Exporting function 'Get-FileHash'.
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Exporting function 'Get-SerializedCommand'.
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Exporting function 'New-TemporaryFile'.
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Creating Folder
VERBOSE: [VALHALLA]: LCM:  [ End    Set      ]  [[MyFolderResource]Test]  in 0.1970 seconds.
VERBOSE: [VALHALLA]: LCM:  [ End    Resource ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]: LCM:  [ End    Set      ]
VERBOSE: [VALHALLA]: LCM:  [ End    Set      ]    in  0.8620 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 1.004 seconds

And just to make sure it’s working, let’s set it to Absent

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windo
ws/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer VALHALLA with user sid S-1-5-21-3449200860-990092898-4058043841-1001.
VERBOSE: [VALHALLA]: LCM:  [ Start  Set      ]
VERBOSE: [VALHALLA]: LCM:  [ Start  Resource ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]: LCM:  [ Start  Test     ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]: LCM:  [ End    Test     ]  [[MyFolderResource]Test]  in 0.1840 seconds.
VERBOSE: [VALHALLA]: LCM:  [ Start  Set      ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Exporting function 'Get-FileHash'.
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Exporting function 'Get-SerializedCommand'.
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] Exporting function 'New-TemporaryFile'.
VERBOSE: [VALHALLA]:                            [[MyFolderResource]Test] File exists and should be absent.  Deleting file
VERBOSE: [VALHALLA]: LCM:  [ End    Set      ]  [[MyFolderResource]Test]  in 0.1590 seconds.
VERBOSE: [VALHALLA]: LCM:  [ End    Resource ]  [[MyFolderResource]Test]
VERBOSE: [VALHALLA]: LCM:  [ End    Set      ]
VERBOSE: [VALHALLA]: LCM:  [ End    Set      ]    in  0.6720 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 0.771 seconds

cVSS Custom Desired State Configuration (DSC) Resource

I will keep this pretty short and sweet. I was asked to create a Custom DSC Resource that could ensure that Volume Shadow Copies was always enabled on a drive. I thought it would be pretty easy to do, but turns out it wasn’t nearly as easy as I thought. When you enable VSS through the GUI there are two things that happen. First, VSS gets enabled on the drive with some default settings. Second, two scheduled tasks are created that match those settings to actually create the shadow copies. Because of that I broke this custom resource into two parts. The cVSS Resource just specifies the drive you want to enable, whether or not you want to enable it, what drive you want to store the shadow copies on, and how much space you want the shadow copies to take up. A Configuration using this Resource looks like this:

Configuration TestVSS{  
 
     Import-DscResource -ModuleName cVSS 
 
     node $YourNode{ 
 
        cVSS SetShadowCopy{ 
          Drive = 'C:' 
          Enable = $True 
          StorageLocation = 'C:' 
          MaxSize = '2048MB' 
       } 
   } 

} 

The cVSSTaskScheduler Resource creates the Scheduled Tasks that create the actual shadow copies on the drive. A Configuration using this Resource looks like this:

Configuration VSSTaskScheduler{

    Import-DscResource -ModuleName cVSS

    node $YourNode{

      cVSSTaskScheduler SevenAM{
          TaskName = "ShadowCopyVolume7AM"
          Ensure = "Absent"
          Drive = "C:"
          TriggerTime = "7:00AM"
      }
      
       cVSSTaskScheduler NineAM{
           TaskName = "ShadowCopyVolume9AM"
           Ensure = "Present"
           Drive = "C:"
           TriggerTime = "9:00AM"
       }
      
       cVSSTaskScheduler Noon{
           TaskName = "ShadowCopyVolumeNoon"
           Ensure = "Present"
           Drive = "C:"
           TriggerTime = "12:00PM"
       }
    }
}

All the files you need to use this Resource can be found on my GitHub site. There is an Examples folder that has the two examples above plus a Configuration using both. The Files folder contains two files that just show how I created (and Updated) the Resource as I went through the process of actually getting it to work. I am very new to the whole GitHub thing so if there are bugs, things that I missed, or a better way of doing something and you want to update or change the files let me know and we can certainly do that.

“Undefined Property ConfigurationName” When Starting a DSC Configuration

Update: This issue is resolved in the November 2014 Update Rollup for Windows RT 8.1, Windows 8.1 and Windows Server 2012 R2. Thanks to Dave Wyatt for the heads up!

So, there I was today, working on a Custom DSC Resource and when I ran Start-DSCConfiguration I got this error:

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsof
t/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer 0497JACOBB with user sid S-1-5-21-2936601924-3794413074-933163191-500.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]
VERBOSE: [DC01]: LCM:  [ End    Set      ]
Undefined property ConfigurationName
 At line:19, char:2
 Buffer:
onName = "TestVSS";
};^
insta
    + CategoryInfo          : SyntaxError: (root/Microsoft/...gurationManager:String) [], CimException
    + FullyQualifiedErrorId : MiClientApiError_Failed
    + PSComputerName        : dc01.arkenstone.com
 
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 0.678 seconds

Well, what the hell does that mean? I just did basically the same thing with another Custom Resource the other day, to the exact same machine and didn’t have any problems. After doing a bunch of different things that didn’t work, I goggled the error and came across this excellent article by Mike Robbins.

I had updated a bunch of Modules and the WMF Framework on my Windows 8.1 desktop to prepare to go through the Advanced DSC MVA Series with Jason Helmick and Jeffrey Snover. This is the version of PowerShell on my Windows 8.1 Machine:

$> $PSVersionTable

Name                           Value                                                                                                                                                          
----                           -----                                                                                                                                                          
PSVersion                      5.0.10018.0                                                                                                                                                    
WSManStackVersion              3.0                                                                                                                                                            
SerializationVersion           1.1.0.1                                                                                                                                                        
CLRVersion                     4.0.30319.34209                                                                                                                                                
BuildVersion                   10.0.9800.0                                                                                                                                                    
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                                                                        
PSRemotingProtocolVersion      2.2   

And here is the version on my Windows 2012 R2 Server:

$PSVersionTable

Name                           Value                                                                                                                                                                                                                             
----                           -----                                                                                                                                                                                                                             
PSVersion                      4.0                                                                                                                                                                                                                               
WSManStackVersion              3.0                                                                                                                                                                                                                               
SerializationVersion           1.1.0.1                                                                                                                                                                                                                           
CLRVersion                     4.0.30319.34003                                                                                                                                                                                                                   
BuildVersion                   6.3.9600.16394                                                                                                                                                                                                                    
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}                                                                                                                                                                                                              
PSRemotingProtocolVersion      2.2                      

If I look at my .MOF file, I have both of the sections Mike talks about in his article. I deleted the lines in the .MOF that said ConfigurationName = “TestVSS” and Name = “TestVSS” and tried to send the Configuration again.
Now, I got a different error, because there are a few other things in the .MOF file that my destination server doesn’t know how to handle. Here is the error:

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsof
t/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer 0497JACOBB with user sid S-1-5-21-2936601924-3794413074-933163191-500.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]
VERBOSE: [DC01]: LCM:  [ End    Set      ]
Undefined property MinimumCompatibleVersion
 At line:26, char:2
 Buffer:
onHost="0497JACOBB";
};^
    + CategoryInfo          : SyntaxError: (root/Microsoft/...gurationManager:String) [], CimException
    + FullyQualifiedErrorId : MiClientApiError_Failed
    + PSComputerName        : dc01.arkenstone.com
 
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 2.215 seconds

Looking at my .MOF file, sure enough I have a couple of extra lines in addition to the ones described in Mike’s article:

 MinimumCompatibleVersion = "1.0.0";
 CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName", 
"MSFT_DSCMetaConfiguration:StatusRetentionTimeInDays"};

If I delete those two lines from my .MOF file, I get what I expected to get all along:

$> Start-DscConfiguration -Wait -Force -Verbose -Path C:\Scripts\TestVSS -ComputerName dc01.arkenstone.com
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsof
t/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer 0497JACOBB with user sid S-1-5-21-2936601924-3794413074-933163191-500.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]
VERBOSE: [DC01]: LCM:  [ Start  Resource ]  [[LutzVSS]SetShadowCopy]
VERBOSE: [DC01]: LCM:  [ Start  Test     ]  [[LutzVSS]SetShadowCopy]
VERBOSE: [DC01]:                            [[LutzVSS]SetShadowCopy] Drive is already enabled for VSS
VERBOSE: [DC01]: LCM:  [ End    Test     ]  [[LutzVSS]SetShadowCopy]  in 0.2340 seconds.
VERBOSE: [DC01]: LCM:  [ Skip   Set      ]  [[LutzVSS]SetShadowCopy]
VERBOSE: [DC01]: LCM:  [ End    Resource ]  [[LutzVSS]SetShadowCopy]
VERBOSE: [DC01]: LCM:  [ End    Set      ]
VERBOSE: [DC01]: LCM:  [ End    Set      ]    in  1.3900 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 1.973 seconds

Now, that’s great that it’s “working”, but since I am wondering if I update the WMF on the target server to the November WMF 5.0 Install, will my Configuration work without any issues? The answer is Yes, it does work without any issues.

Demo DSC – Part 3

In Part 1 of this series I talked about how I demo’d the building of a Domain Controller. In Part 2 I talked about demoing the building of a Pull Server, an App Server, and then using the two servers to show how a Pull Server works and what needs to be done to make the magic happen. If you didn’t read Part 1, here is the disclaimer:

This was never intended to demonstrate all the features and capabilities of DSC (there’s a lot!), but instead was done to show at a high level the kinds of things that are possible and to start a discussion about where it fits into our organization immediately and going forward

My outline for this part of the demo looked like this:

  1. Build Web Server
    1. Run BuildWebServer Script on the Web Server
    2. Talk about what’s going on while the server reboots
      1. File copy after domain join
      2. Install of Roles and Features, IIS Components
  2. Post Reboot
    1. Show IIS Site(s
      1. Show default as stopped
      2. Show DSCTest Website
    2. Browse to site from App Server – http://<WebServerName>:8080
    3. Break Web Server
      1. Change IIS Binding
      2. Delete WebSiteFiles Folder
    4. Show broken site from App Server
    5. Talk about various ways this could be fixed (Push/Pull)
    6. Run the BuildLabWebServer script on the Web Server
    7. Show working site from APP Server

For comedic purposes, here is what my awesome Microsoft Word Website looked like that I break in this demo:

CrappyWebsiteDSCDemo

 

Here is the Configuration script in its entirety. It’s also available on GitHub.

$ConfigData =@{
    AllNodes = @(
        @{
          NodeName = "localhost"
          PSDSCAllowPlainTextPassword = $True
          }
    )

}

Configuration BuildLabWebServer{

    param(
        
        [parameter(Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$NodeName,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$ComputerName,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Domain,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$IP,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$DNSIP,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Gateway,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Subnet,

        [Parameter(Mandatory=$True)] 
        [ValidateNotNullOrEmpty()] 
        [String]$WebSiteName,

        [Parameter(Mandatory=$True)] 
        [ValidateNotNullOrEmpty()] 
        [String]$SourcePath 
    )

    #unsecure, not safe or recommended way to do this
    $Creds = ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force
    $DomainAdminCred = New-Object System.Management.Automation.PSCredential ("$Domain\Administrator", $Creds)
    $SafeModeAdminCred = New-Object System.Management.Automation.PSCredential ("Administrator", $Creds)

    Import-DscResource -ModuleName xNetworking,xComputerManagement,xPendingReboot,LutzResources,xWebAdministration

    Node $NodeName{

        LocalConfigurationManager{
            RebootNodeIfNeeded = $True
        }

        WindowsFeature IIS 
        { 
            Ensure          = "Present" 
            Name            = "Web-Server" 
        } 
 
        WindowsFeature AspNet45 
        { 
            Ensure          = "Present" 
            Name            = "Web-Asp-Net45" 
        }
        
        WindowsFeature IISMgmtTools
        {
            Ensure = "Present"
            Name = "Web-Mgmt-Tools"
        } 
 
        xWebsite DefaultSite  
        { 
            Ensure          = "Present" 
            Name            = "Default Web Site" 
            State           = "Stopped" 
            PhysicalPath    = "C:\inetpub\wwwroot" 
            DependsOn       = "[WindowsFeature]IIS" 
        }       

        xIPAddress WEBIP{
            IPAddress = $IP
            DefaultGateway = $Gateway
            SubnetMask = $Subnet
            AddressFamily = "IPv4"
            InterfaceAlias = "Ethernet"
        }
          
        xDNSServerAddress DomainDNS{
            Address = $DNSIP
            InterfaceAlias = "Ethernet"
            AddressFamily = "IPv4"
        }

        LutzFirewall Domain{
            Profile = "Domain"
            Status = "Disabled"               
        }

        xComputer RenameAndDomainJoin{
           Name = $ComputerName
           DomainName = $Domain
           Credential = $DomainAdminCred
           DependsOn = "[xIPAddress]WEBIP","[xDNSServerAddress]DomainDNS"
       }

       xPendingReboot DomainJoin{
            Name = "Check for reboot after domain join"
       }

        File WebSiteCopy{
            Ensure = "Present"
            DependsOn = "[xPendingReboot]DomainJoin"
            SourcePath = $SourcePath
            DestinationPath = "C:\$WebsiteName"
            Recurse = $True
            Type = "Directory"

       } 
 
        # Create the new Website with HTTP 
        xWebsite DSCWebsite 
        { 
            Ensure          = "Present" 
            Name            = $WebSiteName 
            State           = "Started" 
            PhysicalPath    = "C:\$WebsiteName"
            BindingInfo     = MSFT_xWebBindingInformation 
                             { 
                               Protocol              = "HTTP" 
                               Port                  = 8080 
                             }
            DependsOn = "[File]WebSiteCopy" 
        } 
    }#Node

}#configuration

BuildLabWebServer -ConfigurationData $ConfigData -NodeName localhost -computername $WebServerName -Domain $YourDomain -IP $YourIP -Gateway $YourGateway -Subnet 24 -DNSIP $YourDCIP -OutputPath $YourPath -WebsiteName "DSCTest" -SourcePath "\\PULLSERVER\WebServerFiles"
Set-DscLocalConfigurationManager -Path $YourPath
Get-DSCLocalConfigurationManager
Start-DscConfiguration -Wait -Force -Verbose -Path $YourPath

Demo DSC – Part 2

In Part 1 of this series I talked about how I demo’d the building of a Domain Controller. In Part 2 I am going to talk about how I demo’d building a Pull Server, an App Server, and used the two servers to show how a Pull Server works and what needs to be done to make the magic happen. If you didn’t read Part 1, here is the disclaimer:

This was never intended to demonstrate all the features and capabilities of DSC (there’s a lot!), but instead was done to show at a high level the kinds of things that are possible and to start a discussion about where it fits into our organization immediately and going forward

The outline for this part of my Demo looked like this:

  1. Talk about the purpose of a Pull server (Can also be used to push and write Configurations)
    1. Show how nothing is configured (name, domain, roles/features, etc
    2. Open ISE, Run BuildPullServer Script
    3. Will reboot. While rebooting show the computer account on the DC
    4. Login as domain account
      1. Create share C:\WebServerFiles, share with everyone (explain why we need it later). Explain that this could have been done with DSC, I just choose not to.  This Share will come into play later.
      2. Copy website files to this share (I created a “website” in Word to use with a Web Server, that will come in the last part of this series)

Here is the Configuration script in its entirety.  It’s also available on Github.

$ConfigData =@{
    AllNodes = @(
        @{
          NodeName = "localhost"
          PSDSCAllowPlainTextPassword = $True
          }
    )

}

Configuration BuildLabPullServer{

    param(
        
        [parameter(Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$NodeName,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$ComputerName,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Domain,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$IP,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$DNSIP,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Gateway,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Subnet
    )

    #unsecure, not safe or recommended way to do this
    $Creds = ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force
    $DomainAdminCred = New-Object System.Management.Automation.PSCredential ("$Domain\Administrator", $Creds)
    $SafeModeAdminCred = New-Object System.Management.Automation.PSCredential ("Administrator", $Creds)

    Import-DscResource -ModuleName xActiveDirectory,xNetworking,xComputerManagement,xPendingReboot,xSystemSecurity,xRemoteDesktopAdmin,xTimeZone,xWinEventLog,xPSDesiredStateConfiguration

    Node $NodeName{

        LocalConfigurationManager{
            RebootNodeifNeeded = $True
        }

        xIPAddress PULLServerIP{
            IPAddress = $IP
            DefaultGateway = $Gateway
            SubnetMask = $Subnet
            AddressFamily = "IPv4"
            InterfaceAlias = "Ethernet"
        }

                
        xDNSServerAddress DomainDNS{
            Address = $DNSIP
            InterfaceAlias = "Ethernet"
            AddressFamily = "IPv4"
        }

        File Scripts{
            Ensure = "Present"
            Type = "Directory"
            DestinationPath = "C:\Scripts"
        }

        xIEESC SetAdminIEESC{
            UserRole = "Administrators"
            IsEnabled = $False           
        }

        xUAC UAC{
            Setting = "NeverNotifyAndDisableAll"         
        }

        xTimeZone ServerTime{
            TimeZone = "Central Standard Time"

        }

        xRemoteDesktopAdmin RemoteDesktopSettings
        {
           Ensure = "Present"
           UserAuthentication = "Nonsecure"
        }        
        
        WindowsFeature DSCServiceFeature{
            Ensure = "Present"
            Name = "DSC-Service"        
        } 

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

       xDscWebService PSDSCComplianceServer{
          Ensure = "Present"
          EndpointName = "PSDSCComplianceServer"
          Port = 9080
          PhysicalPath = "$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer"
          CertificateThumbPrint = "AllowUnencryptedTraffic"
          State = "Started"
          IsComplianceServer = $true
          DependsOn = ("[WindowsFeature]DSCServiceFeature","[xDSCWebService]PSDSCPullServer")
        }
        
	    xComputer JoinDomain{
	       Name = $ComputerName
	       DomainName = $Domain
               Credential = $DomainAdminCred
	       DependsOn = "[xIPAddress]PULLServerIP","[xDNSServerAddress]DomainDNS"
           
	    }

       xPendingReboot DomainJoin{
            Name = "Check for reboot after domain join"
       }
    }#Node

}#configuration

BuildLabPullServer -ConfigurationData $ConfigData -NodeName localhost -computername $YourComputerName -Domain $YourDomain -IP $YourIP -Gateway $YourGateway -Subnet 24 -DNSIP $YourDNSIP -OutputPath $YourPath
Set-DscLocalConfigurationManager -Path $YourPath
Get-DSCLocalConfigurationManager
Start-DscConfiguration -Wait -Force -Verbose -Path $YourPath

With that done I then built what I called an App Server. Don’t think that I somehow deployed an Application using DSC (I didn’t) but with a Web Server the last part of my demo I needed to call it something that sort of made sense so I called it an App Server. The build script for the App Server is below, and you can see that it’s much smaller than the previous two build scripts. In this case I wanted to show a minimal configuration for a build script and then demonstrate the process of configuring the App Server to pull a new Configuration.

Here is the Configuration script in its entirety.It’s also available on GitHub.

$ConfigData =@{
    AllNodes = @(
        @{
          NodeName = "localhost"
          PSDSCAllowPlainTextPassword = $True
          }
    )

}

Configuration BuildLabAppServer{

    param(
        
        [parameter(Mandatory)]
        [ValidateNotNullorEmpty()]
        [string]$NodeName,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$ComputerName,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Domain,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$IP,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$DNSIP,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Gateway,

        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Subnet
    )

    #unsecure, not safe or recommended way to do this
    $Creds = ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force
    $DomainAdminCred = New-Object System.Management.Automation.PSCredential ("$Domain\Administrator", $Creds)
    $SafeModeAdminCred = New-Object System.Management.Automation.PSCredential ("Administrator", $Creds)

    Import-DscResource -ModuleName xNetworking,xComputerManagement,xPendingReboot,xActiveDirectory

    Node $NodeName{

        LocalConfigurationManager{
            RebootNodeIfNeeded = $True
        }

        xIPAddress APPIP{
            IPAddress = $IP
            DefaultGateway = $Gateway
            SubnetMask = $Subnet
            AddressFamily = "IPv4"
            InterfaceAlias = "Ethernet"
        }
          
        xDNSServerAddress DomainDNS{
            Address = $DNSIP
            InterfaceAlias = "Ethernet"
            AddressFamily = "IPv4"
        }

        xComputer RenameAndDomainJoin{
           Name = $ComputerName
           DomainName = $Domain
           Credential = $DomainAdminCred
           DependsOn = "[xIPAddress]APPIP","[xDNSServerAddress]DomainDNS"
       }

       xPendingReboot DomainJoin{
            Name = "Check for reboot after domain join"
       }
        
    }#Node

}#configuration

BuildLabAppServer -ConfigurationData $ConfigData -NodeName localhost -computername $YourComputerName -Domain $YourDomain -IP $YourIP -Gateway $YourGateway -Subnet 24 -DNSIP $YourDNS -OutputPath $YourPath
Set-DscLocalConfigurationManager -Path $YourPath
Get-DSCLocalConfigurationManager
Start-DscConfiguration -Wait -Force -Verbose -Path $YourPath

With that done, the next step is to create a Configuration on the Pull Server for the App Server to Pull. All this Configuration does is change the TimeZone on the App Server. Nothing fancy here. There are also some other pieces at the bottom of the Configuration script I should talk about. I have hardcoded a GUID for the server in the Configuration. You can either use this one or change it to your own. I am setting the source and destination paths and sticking the GUID onto the end of the .MOF file, which is required when you are pulling a Configuration. This GUID is how the server knows which Configuration belongs to it (as we will see here shortly). I am then copying the file from the source path to the destination path, and then creating a Checksum file for the .MOF (which is also required).

Here is the Configuration script in its entirety. It’s also available on Github.

Configuration LabAppServerPullConfig{
 
    param(
 
        [string]$ComputerName
 
    )
 
    Import-DscResource -ModuleName xTimeZone
 
    Node $ComputerName{
 
        xTimeZone ServerTime{
            TimeZone = "Central Standard Time"
        }
 
    }#Node
 
}#Config
 
LabAppServerPullConfig -ComputerName $YourComputer -OutputPath $YourPath
 
$GUID = "78653f12-9e40-41fd-9b92-a9c14e3670a7"
 
$source = "$YourPath\LabAppServerPullConfig\$YourComputer.mof"
$dest = "C:\program files\windowspowershell\dscservice\configuration\$guid.mof"
Copy $source $dest
New-DSCChecksum $Dest -Force

With that done, we need to do one other thing before this is going to work. I pre-copied various DSC Resources to the Pull Server, so now we need to .ZIP up the XTimeZone resource so that it can be copied to the App Server when it pulls it’s Configuration. You do this by creating a .ZIP file of the xTimeZone Module and appending the version number to the end of it. In this case, my file name after creating the .ZIP Archive is xTimeZone_1.0.0 . This file then needs to be placed in the “$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules” folder, which is the ModulePath we specified in our Pull Server Configuration. Once that is done, we need to run the command below to also create a Checksum file for this Archive.

New-DSCChecksum -ConfigurationPath "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules\xTimeZone_1.0.0" -Force

Next we need to create a Configuration to tell the App Server to Pull it’s Configuration. This is done by changing the Local Configuration Manager (LCM) settings on the App Server. In my demo I built this Configuration on the Pull Server and then pushed it to the App Server. The outline for this part of the demo looked like this:

  1. Create LCM Configuration. Comment out the Set line in the script. Explain the meta.mof file.
  2. Show LCM Configuration on App Server
  3. Show Consistency Task settings (there should be none)
  4. Push LCM Configuration from Pull Server to App Server
  5. Show LCM Configuration on App Server compared to previous
  6. Show Time Zone. Run Scheduled Task.
  7. Watch App Server for Time Change
    a. Change Time Zone again to something totally random
    b. Run Consistency Task again, watch Time Zone change again

Here is the Configuration script in its entirety.  It’s also available on Github.. You should also note in this script that the Configuration GUID from before makes an appearance here as well. This GUID is what tells the App Server which Configuration to look for on the Pull Server.

Configuration PushLCMConfig{

    param(
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$ComputerName

    )

    Node $ComputerName{
            
        LocalConfigurationManager{
            ConfigurationMode = 'ApplyAndAutoCorrect'
            ConfigurationID = "78653f12-9e40-41fd-9b92-a9c14e3670a7"
            RefreshMode = 'Pull'
            DownloadManagerName = 'WebDownloadManager'
            DownloadManagerCustomData = @{
                #Make sure to change the ServerURL to match what you are using
                ServerUrl = 'http://$PULLSERVER.DOMAIN.COM:8080/PSDSCPullServer.svc';
                AllowUnsecureConnection = 'true' }
            RebootNodeIfNeeded = $True
        }
    }
}

PushLCMConfig -ComputerName $YourComputerName -OutputPath $YourPath
Set-DscLocalConfigurationManager -ComputerName $YourComputerName -Path $YourPath -Verbose

Demo DSC – Part 1

This is the first in a series of posts outlining how I presented a demo of Desired State Configuration (DSC) for the organization I work for. This was never intended to demonstrate all the features and capabilities of DSC (there’s a lot!), but instead was done to show at a high level the kinds of things that are possible and to start a discussion about where it fits into our organization immediately and going forward.

My demo was done using 4 Server 2012 R2 Virtual Machines on a single VMWare ESXi host. Because this environment was in a lab (with some unique networking challenges) and to make things easier for me during the demo I just copied the set of files from a Windows 8.1 machine on the same network as the host onto each VM individually.  I built and ran this demo using Wave 9 DSC Resources.  I switched to Wave 10 halfway through and had a problem with the xComputerManagement Resource (In Wave 10 it doesn’t properly evaluate the condition of whether or not the Computer Names match or not), and switched back to Wave 9 after that to avoid any further problems.  You will also notice in the script that I hardcoded credentials which is definitely not the recommended way to do it in a production environment.

The first thing I wanted to do was to build a Domain Controller on a brand new domain, that would be the foundation for showcasing other features of DSC in the rest of the demo. My outline for this part of the demo looked like this:

  1. Show New Server Build
    1. Show how nothing is configured (name, domain, time zone, IEESC, IP address etc)
    2. Open ISE, Run BuildDC Script. Show computer rename and restart section.
    3. Will restart – Talk about what just happened.
  2. Continue Server Build Post Reboot
    1. Login after reboot, show post Reboot scheduled task kicking off
      1. Show IP address change
      2. Wait for restart again (Approx 3:15 total at this point)
    2. Login after restart with Domain credentials
      1. Show Firewall Status
      2. Event Log Configuration
      3. Time Zone Configuration
  3. Run entire Configuration again to show nothing happens.

Here is the entire BuildDC Configuration Script in it’s entirety.  It’s also available on GitHub.

 

$ConfigData =@{
    AllNodes = @(
        @{NodeName = 'localhost';
          PSDSCAllowPlainTextPassword = $True
          }
    )
 
}
 
Configuration BuildDC{
 
    Param(
 
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$NodeName,
 
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$ComputerName,
 
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Domain,
 
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$IP,
 
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Gateway,
 
        [parameter(Mandatory=$True)]
        [ValidateNotNullorEmpty()]
        [string]$Subnet
 
        #[pscredential]$DomainAdminCred,
        #[pscredential]$SafeModeAdminCred
 
    )#Param
 
    #unsecure, not safe or recommended way to do this
    $Creds = ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force
    $DomainAdminCred = New-Object System.Management.Automation.PSCredential ("Administrator", $Creds)
    $SafeModeAdminCred = New-Object System.Management.Automation.PSCredential ("Administrator", $Creds)
 
    Import-DscResource -ModuleName xActiveDirectory,xNetworking,xComputerManagement,xPendingReboot,xSystemSecurity,xRemoteDesktopAdmin,xTimeZone,xWinEventLog
 
    Node $NodeName{
 
        LocalConfigurationManager{
            RebootNodeifNeeded = $True
        }
 
        xComputer RenameDC{
           Name = $ComputerName
       }
 
        File Scripts{
            Ensure = "Present"
            Type = "Directory"
            DestinationPath = "C:\Scripts"
        }
 
        xIEESC SetAdminIEESC{
            UserRole = "Administrators"
            IsEnabled = $False           
        }
 
        xUAC UAC{
            Setting = "NeverNotifyAndDisableAll"         
        }
 
        xTimeZone ServerTime{
            TimeZone = "Central Standard Time"
 
        }
 
        xRemoteDesktopAdmin RemoteDesktopSettings
        {
           Ensure = 'Present'
           UserAuthentication = 'Nonsecure'
        }
 
        xIPAddress SiteDCIP{
            IPAddress = $IP
            DefaultGateway = $Gateway
            SubnetMask = $Subnet
            AddressFamily = "IPv4"
            InterfaceAlias = "Ethernet"
            DependsOn = "[File]Scripts"
        }
 
        WindowsFeature AD-Domain-Services {
            Ensure = "Present"
            Name   = "AD-Domain-Services"
            DependsOn = "[xIPAddress]SiteDCIP"
        }
        WindowsFeature RSAT-AD-AdminCenter {
            Ensure = "Present"
            Name   = "RSAT-AD-AdminCenter"
        }
        WindowsFeature RSAT-ADDS {
            Ensure = "Present"
            Name   = "RSAT-ADDS"
        }
        WindowsFeature RSAT-AD-PowerShell {
            Ensure = "Present"
            Name   = "RSAT-AD-PowerShell"
        }
        WindowsFeature RSAT-AD-Tools {
            Ensure = "Present"
            Name   = "RSAT-AD-Tools"
        }
        WindowsFeature RSAT-Role-Tools {
            Ensure = "Present"
            Name   = "RSAT-Role-Tools"
        }
        WindowsFeature Telnet-Client{
            Ensure = "Present"
            Name = "Telnet-Client"
        }
 
        Service ADDomainWebServices{
            State = "Running"
            StartupType = "Automatic"
            BuiltInAccount = "LocalSystem"
            Name = "ADWS"
        }
 
        xADDomain BuildSiteDC{
            DomainAdministratorCredential = $DomainAdminCred
            SafeModeAdministratorPassword = $SafeModeAdminCred
            DomainName = $Domain
            DependsOn = "[WindowsFeature]AD-Domain-Services","[Service]ADDomainWebServices"                     
        }
 
        xPendingReboot PostDomainDeploy{
            Name = "Test for reboot after building a domain"
        }
        
        xDNSServerAddress DCDNS{
            Address = $IP
            InterfaceAlias = "Ethernet"
            AddressFamily = "IPv4"
            DependsOn = "[xPendingReboot]PostDomainDeploy"
        }
        
        xWinEventLog DirectoryService{
            LogName = "Directory Service"
            DependsOn = "[xDNSServerAddress]DCDNS"
            LogMOde = "Circular"
            MaximumSizeInBytes = 16MB
        }
        
 
    }#Node
 
 
}#Configuration
 
BuildDC -NodeName localhost -Domain YourDomain.com -IP $SomeIP -Gateway $SomeGateway -Subnet 24 -OutputPath C:\Scripts\BuildDC -ConfigurationData $ConfigData -ComputerName $YourComputerName
Set-DscLocalConfigurationManager -Path $YourPath
Get-DSCLocalConfigurationManager
Start-DscConfiguration -Wait -Force -Verbose -Path $YourPath

Exploring the PowerShell DSC xPendingReboot Resource

While building a DSC Demo for the new job this week I got the chance to explore using many of the “new” Resources that have been released.  One of those Resources is the xPendingReboot which I am going to talk about here, because the documentation wasn’t very clear (to me anyways after having been away from DSC for a long time) on how to use it properly.

The TechNet article on the Resource and an article by the Scripting Guy can be found at the links below.

https://gallery.technet.microsoft.com/scriptcenter/xPendingReboot-PowerShell-b269f154

http://blogs.technet.com/b/heyscriptingguy/archive/2014/10/15/use-powershell-dsc-to-check-pending-reboot.aspx

If you just look at it, you would assume you could do something like this to check for a reboot:

Configuration TestPendingReboot{

    Param(
        [string]$ComputerName='localhost'
    )

    Import-DSCResource -ModuleName xPendingReboot

    Node $ComputerName{

        xPendingReboot PreTest{
            Name = "Check for a pending reboot before changing anything"
        }
        
    }

}

However, you would be wrong! If we create the .MOF file for this Configuration and run this against the local system (which has a reboot pending after a computer rename), the system doesn’t actually reboot itself, it just notifies you that a reboot is pending.

TestPendingReboot -OutputPath C:\Scripts\TestPendingReboot
Start-DscConfiguration -Wait -Force -Verbose -Path .\TestPendingReboot

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 WIN-74EKGETUJS6 with user sid S-1-5-21-2712606644-3520791333-1947032181-500.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Set      ]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Resource ]  [[xPendingReboot]PreTest]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Test     ]  [[xPendingReboot]PreTest]
VERBOSE: [WIN-74EKGETUJS6]:                            [[xPendingReboot]PreTest] A pending reboot was found for PendingComputerRename.
VERBOSE: [WIN-74EKGETUJS6]:                            [[xPendingReboot]PreTest] Setting the DSCMachineStatus global variable to 1.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Test     ]  [[xPendingReboot]PreTest]  in 0.4020 seconds.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Set      ]  [[xPendingReboot]PreTest]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Set      ]  [[xPendingReboot]PreTest]  in 0.0150 seconds.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Resource ]  [[xPendingReboot]PreTest]
VERBOSE: [WIN-74EKGETUJS6]:                            [] A reboot is required to progress further. Please reboot the system.
WARNING: [WIN-74EKGETUJS6]:                            [] A reboot is required to progress further. Please reboot the system.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Set      ]    in  3.2110 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 3.763 seconds

Well, that’s great and all but it didn’t reboot the machine like I needed it to. Looking at those examples, maybe I need to add the LocalConfigurationManager piece to make this work?

Configuration TestPendingReboot{

    Param(
        [string]$ComputerName='localhost'
    )

    Import-DSCResource -ModuleName xPendingReboot

    Node $ComputerName{

        xPendingReboot PreTest{
            Name = "Check for a pending reboot before changing anything"
        }

        LocalConfigurationManager{
            RebootNodeIfNeeded = $True
        }
        
    }

}

When you build this Configuration you will immediately notice you get a localhost.mof as well as a localhost.meta.mof . The Meta.mof is a result of making a change to the Local Configuration Manager (LCM) and should be a hint that something needs to be done with it :). The TechNet article uses a RebootNodeifNeeded = ‘True’ instead of the Boolean $True, which is not correct. If you try to build the Configuration using ‘True’ you get this error:

ConvertTo-MOFInstance : System.ArgumentException error processing property 'RebootNodeIfNeeded' OF TYPE 'LocalConfigurationManager': Cannot convert value "System.String" to type "System.Boolean". Boolean parameters accept only 
Boolean values and numbers, such as $True, $False, 1 or 0.
At line:285 char:16
+     $aliasId = ConvertTo-MOFInstance $keywordName $canonicalizedValue
+                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Write-Error], InvalidOperationException
    + FullyQualifiedErrorId : FailToProcessProperty,ConvertTo-MOFInstance
Errors occurred while processing configuration 'TestPendingReboot'.
At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:3189 char:5
+     throw $errorRecord
+     ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (TestPendingReboot:String) [], InvalidOperationException
    + FullyQualifiedErrorId : FailToProcessConfiguration

I am going to ignore the localhost.meta.mof file for now and just try this again to see what happens. And the exact same thing happens. If you are wondering if moving the LocalConfigurationManager section ahead of the xPendingReboot section matters or will help, it won’t. You actually need to change the LCM setting on the computer before starting the Configuration, because right now it is set to this. Notice the RebootNodeIfNeeded section at the bottom:

PS C:\Scripts> Get-DscLocalConfigurationManager


ActionAfterReboot              : ContinueConfiguration
AllowModuleOverWrite           : False
CertificateID                  : 
ConfigurationDownloadManagers  : {}
ConfigurationID                : 
ConfigurationMode              : ApplyAndMonitor
ConfigurationModeFrequencyMins : 15
Credential                     : 
DebugMode                      : {NONE}
DownloadManagerCustomData      : 
DownloadManagerName            : 
LCMCompatibleVersions          : {1.0, 2.0}
LCMState                       : PendingReboot
LCMVersion                     : 2.0
MaxPendingConfigRetryCount     : 
StatusRetentionTimeInDays      : 10
PartialConfigurations          : {}
RebootNodeIfNeeded             : False
RefreshFrequencyMins           : 30
RefreshMode                    : PUSH
ReportManagers                 : {}
ResourceModuleManagers         : {}
PSComputerName    

You do that by using this command:

Set-DscLocalConfigurationManager -Path .\TestPendingReboot -Verbose
VERBOSE: Performing the operation "Start-DscConfiguration: SendMetaConfigurationApply" on target "MSFT_DSCLocalConfigurationManager".
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendMetaConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguratio
n'.
VERBOSE: An LCM method call arrived from computer WIN-74EKGETUJS6 with user sid S-1-5-21-2712606644-3520791333-1947032181-500.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Set      ]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Resource ]  [MSFT_DSCMetaConfiguration]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ Start  Set      ]  [MSFT_DSCMetaConfiguration]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Set      ]  [MSFT_DSCMetaConfiguration]  in 0.0160 seconds.
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Resource ]  [MSFT_DSCMetaConfiguration]
VERBOSE: [WIN-74EKGETUJS6]: LCM:  [ End    Set      ]    in  0.0160 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Set-DscLocalConfigurationManager finished in 0.09 seconds.

Get-DscLocalConfigurationManager


ActionAfterReboot              : ContinueConfiguration
AllowModuleOverWrite           : False
CertificateID                  : 
ConfigurationDownloadManagers  : {}
ConfigurationID                : 
ConfigurationMode              : ApplyAndMonitor
ConfigurationModeFrequencyMins : 15
Credential                     : 
DebugMode                      : {NONE}
DownloadManagerCustomData      : 
DownloadManagerName            : 
LCMCompatibleVersions          : {1.0, 2.0}
LCMState                       : Ready
LCMVersion                     : 2.0
MaxPendingConfigRetryCount     : 
StatusRetentionTimeInDays      : 10
PartialConfigurations          : {}
RebootNodeIfNeeded             : True
RefreshFrequencyMins           : 30
RefreshMode                    : PUSH
ReportManagers                 : {}
ResourceModuleManagers         : {}
PSComputerName                 : 

Now when we start the Configuration, we get the exact same result as above, plus an automatic reboot!
PendingReboot






If you want everything in one file, you can find it here.

Moving SCOM 2012 R2 Data Warehouse Database to New SQL Server Cluster

This is the second of two posts in regards to moving Operations Manager 2012 R2 Databases.  The first post about moving the OperationsManager database can be found here.  I won’t rehash any of that information but will instead just get right into the How To portion.  The TechNet article for moving the SCOM DataWarehouse Database can be found here.

  1. Stop the following services on OPSMGR01 and change their type to Manual.  Otherwise they will restart and cause you problems.
    1. System Center Data Access
    2. System Center Management Configuration
  2. Stop the following services on OPSMGR02 and change their type to Manual.  Otherwise they will restart and cause you problems.
    1. System Center Data Access
    2. System Center Management Configuration
  3. On OLDSQLServer, use SQL Server Management Studio to create a full backup of the data warehouse database. The default name is OperationsManagerDW. We recommend that you also back up the associated master database.
    1. Copy backup file to \\SQLCLUSTERNODE1\SHARE
  4. Open SQL Management Studio on SQLCLUSTERNODE1 and connect to SQLCLUSTER\SCOMDW Instance
    1. Restore OperationsManagerDW Database using SQL
  5. Backup registry on OPSMGR01
  6. Backup registry on OPSMGR02
  7. Update Registry on OPSMGR01
    1. Regedit
    2. Navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup
    3. For each of the following keys, double-click the name, change the value to the hostname of the SQL Server-based computer now hosting the operational database, and then click OK to save your changes.
      1. DatabaseWarehouseDBServerName: SQLCLUSTER\SCOMDW
      2. DatabaseName: OperationsManagerDW
  8. Update Registry on OPSMGR02
    1. Regedit
    2. Navigate to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup
    3. For each of the following keys, double-click the name, change the value to the hostname of the SQL Server-based computer now hosting the operational database, and then click OK to save your changes.
      1. DatabaseWarehouseDBServerName: SQLCLUSTER\SCOMDW
      2. DatabaseName: OperationsManagerDW
  9. Login to the server you are using for reporting off of the SCOM Data Warehouse
    1. HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\System Center Operations Manager\3.0\Reporting,\ DWDBInstance double-click the name and change the value to the hostname of the SQL Server-based computer now hosting the operations manager DW database, and then click OK to save your change.
      1. In my case that would be SQLCLUSTER\SCOMDW
    1. Start the System Center Data Access Service on OPSMGR01 associated with the reporting server. This is needed to access the reports page
    2. On reporting server, change the connection strings.
      1. Open a browser and go to the reporting webpage
      2. Click Show Details and then click Data Warehouse Main. Change the Connection String to contain the new data warehouse server name, and then click Apply. SQLCLUSTER\SCOMDW.
      3. Test the connection
      4. Click Application monitoring, and then click .NET monitoring.
      5. Click AppMonitoringSource.
      6. On the AppMonitoringSource page, click Properties and change Connection string to contain SQLCLUSTER\SCOMDW, and then click Apply.
      7. Test the connection
      8. Close the browser.
    3. On SQLCLUSTERNODE1 update the OperationsManager database table. (Note that this is NOT the DataWarehouse instance!)
      1. Open SQL Server Management Studio. SQLCLUSTER\SCOM
      2. Expand Databases, OperationsManager, and Tables.
      3. Right-click dbo.MT_Microsoft$SystemCenter$DataWarehouse, and then click Edit Top 200 Rows.
      4. Change the value in the MainDatabaseServerName_2C77AA48_DB0A_5D69_F8FF_20E48F3AED0F column to SQLCLUSTER\SCOMDW
    4. Update the OperationsManager database for Application Performance Monitoring functionality.
      1. Right-click dbo.MT_Microsoft$SystemCenter$DataWarehouse$AppMonitoring, and then click Edit Top 200 Rows.
      2. Change the value in the MainDatabaseServerName_5C00C79B_6B71_6EEE_4ADE_80C11F84527A column to SQLCLUSTER\SCOMDW
      3. Do the same for the following tables.
      4. Right-click dbo. MT_Microsoft$SystemCenter$DataWarehouse$AppMonitoring_Log and then click Edit Top 200 Rows. Change the value of column Post_MainDatabaseServerName_5C00C79B_6B71_6EEE_4ADE_80C11F84527A to SQLCLUSTER\SCOMDW
      5. Right-click dbo.MT_Microsoft$SystemCenter$DataWarehouse_Log and then click Edit Top 200 Rows. Change the value of column.Pre_MainDatabaseServerName_2C77AA48_DB0A_5D69_F8FF_20E48F3AED0F TO SQLCLUSTER\SCOMDW.
      6. Close SQL Server Management Studio.
    5. On SQLCLUSTERNODE1, update the member database.
      1. Open SQL Server Management Studio and connect to SQLCLUSTER\SCOMDW instance.
      2. Expand Databases, OperationsManagerDW, and Tables.
      3. Right-click dbo.MemberDatabase, and then click Edit Top 200 Rows.
      4. Change the value in the ServerName column to reflect the name of the new SQL Server, SQLCLUSTER\SCOMDW
      5. Close SQL Server Management Studio.
    6. Update security credentials on SQLCLUSTER\SCOMDW
      1. All of my security credentials carried over and I had no issues.  However, your environment may be completely different.  Definitely make sure to read this section in the TechNet documentation
    7. Stop SQL Services relating to SCOM on OLDSQLServer
    8. Open PowerShell.  Type Write-Host “Bye Kitty” to sacrifice a kitten
    9. Restart OPSMGR01 and OSPMGR02 (this was to ensure registry setting changes were picked up)
    10. Start the following services on OPSMGR01 and change their startup type to Automatic
      1. System Center Data Access
      2. System Center Management Configuration
    11. Start the following services on OPSMGR02 and change their startup type to Automatic
      1. System Center Data Access
      2. System Center Management Configuration
    12. Spam refresh OperationsManager event log on OPSMGR01.  Hopefully you see lots of informational messages and no errors about connections to the SQL Server.

To verify a successful move of the Operations Manager Data Warehouse Database:

  1. Verify that you can successfully run a report from the console.
  2. Ensure that the health state of all management servers in the management group are Healthy.
  3. If the health state of any management server is Critical, open Health Explorer, expand Availability – <server name>, and then continue to expand until you can navigate to Data Warehouse SQL RS Deployed Management Pack List Request State. Check the associated events to determine if there is an issue accessing the data warehouse database.
  4. Check operating system events:
    1. Open the operating system’s Event viewer. Navigate to Event Viewer, and then to Operations Manager.
    2. In the Operations Manager pane, search for events with a Source of Health Service Module and a Category of Data Warehouse.
  1. The move was successful if event number 31570, 31558, or 31554 exists.
  2. There is an issue accessing the data warehouse database if event numbers 31563, 31551, 31569, or 31552 exists.
  3. Check events in Operations Manager:
    1. In the Operations console, select Monitoring.
    2. Navigate to Monitoring, Operations Manager, Health Service Module Events, and then to Performance Data Source Module Events.
    3. Search the Performance Data Source Module Events pane for events with a Date and Time that is later than the move.