Finding All Client Mailboxes in the Office 365 Partner Portal

Someone at work asked me “how many client mailboxes are we supporting in Office 365?”. It’s easy to tell how many clients you are supporting, as soon as you login to the partner portal it tells you that number. I had never tried to get the number of client mailboxes before, but I strongly suspected that I could do it using PowerShell. Turns out I was right!

The first thing you will need to do is to connect to Office 365 using PowerShell. If you haven’t done this before, go to this page and download the Office 365 PowerShell Module and install it.

After that is done, execute the commands below which will connect you to Office 365. It’s important to note that the credential you use must have delegated admin access to all the client accounts or the steps in this article won’t work.

Once that is done running the commands below will provide a list of all the commands in the MSOnline Module as well as only those commands which contain the word partner. I was hoping there was a cmdlet that would allow me to get all my clients information which I could then iterate through to get a list of all mailboxes.

MSOnlinePartner

The Get-MSOLPartnerContract looks interesting, lets try that.

MSOnlinePartnerContract

Well, that gets me a lot of Tenant ID’s, which doesn’t appear to be useful. Or does it? I know from using Office 365 with PowerShell in the past that there are commands that use the TenantID parameter to retrieve information.

Doing some more investigation I run the series of commands below. First I am storing all the Tenant ID’s in a $Clients variable, and then using Get-Member to see what properties this variable contains. Seeing that one of them is Default Domain Name, I run $Clients.DefaultDomainName which sure enough enumerates all of my client domain names so I can see that they are in fact my clients. I am then storing all the Tenant ID’s in another variable $Tenants which I will be using shortly.

Earlier I mentioned that I knew from past experience that there were cmdlets in the MSOnline module that used TenantID as a parameter. How could I find out what those commands are? By running the command below. I reduced the output by using -Verb Get since I am going to be getting the number of mailboxes.

Scrolling through the list I see that Get-MSOLUser is one of the cmdlets that uses TenantID as a parameter and think that might be a good place to start. I take one of my Tenant ID’s in my $Tenants variable and run the command below.

I was hoping to see that there was a domain or client name property, but unfortunately there is not. So in order to figure that out I ran the commands below. What I am doing here is taking the first user that comes up for the tenant and getting their User Principal Name (UPN). Since each client’s users have the same UPN, this will be OK. After that I am splitting the UPN at the ‘@’ symbol and using that information for the domain. This is definitely not the only way to do this (or probably the best) but it worked for me.

Next I am creating a hashtable with my properties and then creating a new PSObject Object and saving this information to a .CSV file. Notice in the properties section that for the Domain I am having to use $Domain[1] because when it split the UPN at the ‘@’ symbol it split it into two pieces, the section before the ‘@’ and the section after it.

Finally, I have tied all this into a Function I am calling Get-PartnerMailboxes that will iterate through all my Tenant ID’s and create a .CSV listing the domain and total number of licensed users for each tenant.

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.

Warning: Unable to find package provider ‘PSModule’ on Windows 10

Running Windows 10 Enterprise Preview Build 5.0.10158.0

When I run Find-Module, I receive the error message in the screenshot below.
CIX7pMsWEAAgUqK

After bashing my face against the problem for a while, I got some help from Ben Gelens, who provided the answer on how to fix the problem:

Get-PackageProvider -Name PSModule -ForceBootstrap

Once that is done, close and reopen the ISE (or the Console host) and you are good to go!

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.

Why Should I Attend the PowerShell Summit?

Starting last fall for the European PowerShell Summit and this year for the North America PowerShell Summit all the session recordings are available on YouTube. Because of that you may be asking yourself “Why should I go to the Summit when I can just watch everything online?”. Here are ten reasons, in no particular order other than the order in which my brain dumped them out.

  1. You get direct interaction with the product team. And I am not talking about members of the PowerShell team sitting in a corner not interacting with anyone.  They are there to get as much feedback as possible, learn how people are using it, and to directly interact with members of the community.  This isn’t the Microsoft of 10 years ago or two years ago.  When they say they want your feedback (and not just the good stuff) they absolutely mean it.  Special thanks to Lee Holmes, Michael Greene, Joey Aiello, Angel Calvo, Hemant Mahawar, and Kenneth Hansen for making the trip out to Charlotte.
  2. I don’t care how much you interact with other members of the community over Twitter, Email, Google Hangouts, whatever.  There is no substitute for meeting people face to face, shaking their hand and getting to know something about them besides how they use PowerShell.
  3. And combining #1 and #2, you also get to talk to (and listen to) people talk about how they have solved problems using PowerShell, and what their thought process was around creating that solution. You can then ask people, I have this problem, what would you do to solve it?  One of those conversations alone is worth the price of admission.
  4. You get to watch Mike Robbins “harass” Rohn Edwards all week by telling everyone how great his sessions are going to be and how everyone needs to go to them.  By all accounts they were awesome.
  5. You get to see the look on Dave Wyatt’s face Microsoft announces that “his code” (Pester) is shipping with the next version of Windows Server
  6. You get to have Steven Murawski answer your questions about creating Custom DSC Resources while you are creating them
  7. You get a free Chef T-Shirt, courtesy of Steve.
  8. You get awesome Nano Server and PowerShell stickers courtesy of the one and only Jeffrey Snover.
  9. You get to watch Jason Helmick live and in person talk about how he has his depends on.
  10. You end up finding a bug in Class based DSC Resources that you only found because you participated in the DSC Resource hack-a-thon at the PowerShell Summit.  So make sure you vote on that!
  11. Bonus!  Jeff Hicks gives you a signed PowerShell Deep Dives book and a 30 day Pluralsight subscription to give away at your next user group meeting.
  12. Bonus!  You get to watch Jeffrey Snover demonstrate and talk about a bunch of stuff I can’t repeat or talk about upon fear of death :).
  13. Bonus!  You get to talk to (and listen) to June Blender talk about PowerShell, PowerShell Help, and writing.  Her passion and knowledge around those topics is unbelievable.
  14. Bonus!  You learn how little you really know about PowerShell.  This is a good thing!  This is also something I relearn on a nearly daily basis.

I was also asked by Josh Duffney on Twitter what I thought were some of the “must watch” videos from the Summit.  The lame answer is “everything”, but that’s also not realistic.  If you put a gun to my head and said “you have to pick 7 sessions” here are the 7 I would pick (no particular order).  All the Summit videos can be found in this playlist on YouTube.

  1. Kenneth Hansen & Angel Calvo PowerShell Team Engagement
  2. Don Jones DSC Resource Design
  3. Dave Wyatt on Automated Testing using Pester
  4. Defending the Defenders Part 1 & 2
  5. Debugging
  6. PowerShell Get
  7. Ashley McGlone on DSC and AD
  8. PowerShell v5 Debugging (There is also a session on Debugging PowerShell by Kirk Munro.  These are different)

ValidateSet for a Parameter in a DSC Class Based Resource Fails to Throw Error

While working on a Custom DSC Resource that I started Monday night at the PowerShell Summit I came across some interesting behavior that turned out to be a bug in the WMF 5.0 February Preview. I have logged this issue in Connect, but I wanted to write a blog post to demonstrate what exactly is going on for when someone else runs into this issue. I am just going to the use Custom DSC Resource for creating a Primary DNS Zone that I was working on as the example to demonstrate the behavior.

Here are the properties for the resource. I figured I could just do ValidateSet like I always had done for an advanced function or non-class based DSC Resource.

    [DscProperty(Key)]
    [string]$ZoneName
    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure

    [DscProperty()]
    [ValidateSet("Forest","Domain","Current","Legacy")]
    [string] $ReplicationScope

    [DscProperty(Mandatory)]
    [bool] $Aging

My Configuration for testing the Resource looks like this:

Configuration TestDNS{

    Import-DSCResource -ModuleName cDNSPrimaryZone
   
    node localhost{

        cDNSPrimaryZone TestZone{

            ZoneName = "Test"
            Ensure = "Present"
            ReplicationScope = "Domain"
            Aging = $True
        }

    }

}

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

If I run this Configuration, with one of the appropriate values for ReplicationScope, it works exactly like you would expect it to.

Directory: C:\Scripts\TestDNS


Mode                LastWriteTime         Length Name                                                                                     
----                -------------         ------ ----                                                                                     
-a----        4/22/2015   4:08 AM           1746 localhost.mof                                                                            
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocal
ConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer DC01 with user sid S-1-5-21-3991266330-1997624308-690668430-500.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]
VERBOSE: [DC01]:                            [DSCEngine] Importing the module C:\Program Files\WindowsPowerShell\Modules\cDNSPrimaryZone\
cDNSPrimaryZone.psd1 in force mode.
VERBOSE: [DC01]: LCM:  [ Start  Resource ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]: LCM:  [ Start  Test     ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Importing the module cDNSPrimaryZone in force mode.
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Checking to see if the Zone Test exists
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Zone Test does not exist
VERBOSE: [DC01]: LCM:  [ End    Test     ]  [[cDNSPrimaryZone]TestZone]  in 1.5350 seconds.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Importing the module cDNSPrimaryZone in force mode.
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Checking to see if the Zone Test exists
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Adding DNS Server Zone Test
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] AllowUpdate successfully set on server DC01.
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Aging successfully set on server DC01.
VERBOSE: [DC01]: LCM:  [ End    Set      ]  [[cDNSPrimaryZone]TestZone]  in 0.7220 seconds.
VERBOSE: [DC01]: LCM:  [ End    Resource ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]: LCM:  [ End    Set      ]    in  3.8620 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 3.932 seconds

That’s great. But, what happens if I put in a value that isn’t part of Validate Set?

Configuration TestDNS{

    Import-DSCResource -ModuleName cDNSPrimaryZone
   
    node localhost{

        cDNSPrimaryZone TestZone{

            ZoneName = "Test"
            Ensure = "Present"
            ReplicationScope = "HokeyPokey"
            Aging = $True
        }

    }

}

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

Directory: C:\Scripts\TestDNS


Mode                LastWriteTime         Length Name                                                                                     
----                -------------         ------ ----                                                                                     
-a----        4/22/2015   4:13 AM           1754 localhost.mof                                                                            
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocal
ConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer DC01 with user sid S-1-5-21-3991266330-1997624308-690668430-500.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]
VERBOSE: [DC01]:                            [DSCEngine] Importing the module C:\Program Files\WindowsPowerShell\Modules\cDNSPrimaryZone\
cDNSPrimaryZone.psd1 in force mode.
VERBOSE: [DC01]: LCM:  [ Start  Resource ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]: LCM:  [ Start  Test     ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Importing the module cDNSPrimaryZone in force mode.
VERBOSE: [DC01]: LCM:  [ End    Test     ]  [[cDNSPrimaryZone]TestZone]  in 3.2480 seconds.
VERBOSE: [DC01]: LCM:  [ Start  Set      ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]:                            [[cDNSPrimaryZone]TestZone] Importing the module cDNSPrimaryZone in force mode.
VERBOSE: [DC01]: LCM:  [ End    Set      ]  [[cDNSPrimaryZone]TestZone]  in 3.0840 seconds.
VERBOSE: [DC01]: LCM:  [ End    Resource ]  [[cDNSPrimaryZone]TestZone]
VERBOSE: [DC01]: LCM:  [ End    Set      ]    in  14.0760 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 19.845 seconds

That is clearly not what should happen. You would expect to see an error saying something to effect of “HokeyPokey does not belong to the set “Domain”,”Forest”,”Current”,”Legacy”, it needs to be one of those values”.

If we look at the .MOF file that gets created, this incorrect value also makes it into the .MOF file:

instance of cDNSPrimaryZone as $cDNSPrimaryZone1ref
{
ResourceID = "[cDNSPrimaryZone]TestZone";
 Aging = True;
 Ensure = "Present";
 ZoneName = "Test";
 SourceInfo = "C:\\Program Files\\WindowsPowerShell\\Modules\\cDNSPrimaryZone\\Examples\\DNSTest.ps1::7::9::cDNSPrimaryZone";
 ModuleName = "cDNSPrimaryZone";
 ReplicationScope = "HokeyPokey";
 ModuleVersion = "1.0";

 ConfigurationName = "TestDNS";

};

You can tell that it knows something is wrong, because when it runs through Test-TargetResource and Set-TargetResource it doesn’t actually do anything (notice all the Verbose messages that are missing from the previous example), but it also doesn’t error.

So how do we get around this? By using an Enum!

enum ReplicationScope
{
    Domain
    Forest
    Current
    Legacy
}

enum Ensure 
{ 
    Absent 
    Present 
}

[DscResource()]
class cDNSPrimaryZone{

    [DscProperty(Key)]
    [string]$ZoneName
    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure

    [DscProperty(Mandatory)]    
    [ReplicationScope] $ReplicationScope

    [DscProperty(Mandatory)]
    [bool] $Aging

Now, if I try to set the ReplicationScope to HokeyPokey, we get the behavior we would expect.

cDNSPrimaryZone\cDNSPrimaryZone : At least one of the values 'HokeyPokey' is not supported or valid for property 'ReplicationScope' on 
class 'cDNSPrimaryZone'. Please specify only supported values: 
Domain, Forest, Current, Legacy.
At C:\Program Files\WindowsPowerShell\Modules\cDNSPrimaryZone\Examples\DNSTest.ps1:7 char:9
+         cDNSPrimaryZone TestZone{
+         ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Write-Error], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnsupportedValueForProperty,cDNSPrimaryZone\cDNSPrimaryZone
 
Errors occurred while processing configuration 'TestDNS'.
At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:3189 char:5
+     throw $errorRecord
+     ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (TestDNS:String) [], InvalidOperationException
    + FullyQualifiedErrorId : FailToProcessConfiguration

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