Part II - Deploying PowerShell GUIs in Minutes using Visual Studio

April 16, 2015 FoxDeploy

Learning PowerShell GUIs

This post is part of the Learning GUI Toolmaking Series, here on FoxDeploy. Click the banner to return to the series jump page!


I got a lot of feedback last time, everyone wants the rest of the series, and you guys want it now! So I’m skipping my normal 1,000 word limit for this post and making this bad-boy LONG! There will still be a part three where I’ll show you how to use some of the trickier form elements. Some of them are absolute hacks to make them work in PowerShell, so if you’re the god of XAML and WPF, please have mercy on us mere-mortals and add some comments to let us know how I ought to be doing it.

Let’s jump back in and take our finished XAMl from last time and put it into PowerShell.

Whoa whoa, what’s XAML

I don’t know if you noticed this window. This whole time we’ve been adding elements, dropping boxes and text and things like that, it’s been updating in real time!

ZAMMLE_pic0

The language here is XAML, (Extensible Application Markup Language) which is a Microsoft language built on XML to make very small but effective applications easily. We’ll copy this code, but first, we need to make some changes to it before it will ‘just work’.

If you want to use my example as the base for your GUI, you can copy this right into Visual Studio:

Basically, we need to remove some properties from the window declaration, and also remove the x: before each name. But don’t do all of that by hand, just copy and paste it into this little blob I’ve made for you :)

Importing this into PowerShell

Updated January 2018 with better error catching!

If you’ve taken a look at the blog article on the MS site which I linked last week, some of the above might look very familiar. In fact, I’ve added a bit of extra output to help us in troubleshooting (by dumping a list of all of the variables relating to objects on our form) and made this into a snippet, which you can download and embed in your own ISE.

That’s a lot of code, but you only need to copy and paste your XAML from VisualStudio to PowerShell in between the here-string.

What’s a here-string?

If you’ve never heard of a here-string, it’s a programming term for a multi-line variable that maintains spacing and allows for variable expansion. Long-story short, it’s the @” “@ above.

When you’ve copied and pasted your XAML into the here-string above and hit F5 in the PowerShell ISE (or wherever you edit scripts), you’ll see the following:

Running Convert tool

Our tool has scanned through the GUI and created hooks associated with every interactable element on the screen. We can now make changes to these things with code, just by changing their objects. This little display here is actually a function embedded in the code. You can run it again later if you forget the variable names by running Get-FormVariables.

As you see above, if we want to run the GUI, we just type in

>$Form.ShowDialog() | Out-Null

That was SO EASY!

Changing values on the GUI is easy!

Now, what if we wanted to do something a bit more complex, like change the value of the Text where it says ‘TextBox’.

We need to hook into the properties that the tool displayed to us earlier. In this case, the name is $WPFTextBox. This is an object which this refers to the object on our form. That means we can change the text just by looking for a .text property on this object.

$WPFtextBox.Text >TextBox

If we change this with a simple equals statement…

$WPFtextbox.Text = 'Hello World'

Hello World

This is the basic flow we’ll take to interact with all of our GUIs hence-forth. Draw something cool in Visual Studio, copy and paste it into the snippet, then run Get-FormVariables and see what the name is for our new cool GUI features (they’re called ‘Form controls’ if we want to be specific). Then look at the new object and see what it’s properties and methods are.

But it doesn’t work…

One last thing before we fully upgrade this into an awesome tool, let’s try clicking the OK Button.

Nothing happens! Here’s why: by default, we need to give our button an action to run when we click it. You do this using the Add_Click() method, which lets us put a {script-block} into the () overload, which will be executed when the user clicks a button. This makes our buttons a great place to setup hooks if we want to grab the values from a box.

For instance, if we want our OK button to just close our form, we run this little number

$WPFbutton.Add_Click({$form.Close()})

After the form has closed, the value of all of these objects in the form still persist, so if the user made typed something like ‘YoDawg’ into our textbox, we can get it once the form is gone by running:

$WPFtextBox.Text

YoDawg

Alright, let’s make this into a WMI info gathering tool.

Building a better WMI tool

I’ve got a tool that I walk people through making in my Learning PowerShell bootcamp course (if you want me to come deliver one at your company, send me a message!), in which we learn how to query WMI/CIM and then expand from there and make it do cool fancy stuffs. The output of the tool at the end of the day looks like this.

Driveinfo

We can make a GUI version of this by adding a ListView, which is pretty much embedding something like an Excel datasheet into this GUI. To do this, click ListView in the ToolBox and drag it to your form and size appropriately. You’ll notice that I also made a few tweaks to the layout to better fit what we want this tool to do.

Layout pregridview

You can just barely make it out, but there is a grid there now, waiting to receive our beautiful rows and columns. Let’s add some stuff, just a warning, this can be a bit tricky at first.

In Visual Studio, in the XAML, click the GridView Tag.

Layout Click Gridview

In properties on the right, click ‘Columns’

Layout AddColumns

This will bring up the Column collection editor

Column editor

Now, in this area, you’ll want to click the ‘Add’ button and change the width to about 100 for each, and specify the column name in the Header Box. I’ll add one each for each of the fields my tool returns:

• Drive Letter • Drive Label • Size(MB) • FreeSpace%

As before, you can change the font by clicking on the area with text, then go to Properties>Text on the right side of the screen. When finished you should have something like this:

Columns

If we want to make our buttons and form actually work though, we’ll need to hook into the form again, as we did previously.

Making all of the new stuff work

If you want to catch up with where we are now in the walkthrough, get this stuff:

https://gist.github.com/1RedOne/482fbcfdb6a081bafa1625fdb9e21249

If you scroll to the bottom, just below Get-FormVariables, you’ll see an example of how to add data to a field. This part of our script is where the XAML has been parsed, and objects have been created to hook into them. This is where we’ll need to put our magic sauce to make the buttons and fields work and do cool things.

Where-To-make-changes

So, scroll down to the ‘Make the Objects Actually Work’ area.

First things first, take this snippet of code which accepts a computer name and returns the disk information:

Function Get-DiskInfo {
param($computername =$env:COMPUTERNAME)
 
Get-WMIObject Win32_logicaldisk -ComputerName $computername | 
  Select-Object @{Name='ComputerName';Ex={$computername}},`
          @{Name=Drive Letter;Expression={$_.DeviceID}},`
          @{Name=Drive Label;Expression={$_.VolumeName}},`
          @{Name=Size(MB)’;Expression={[int]($_.Size / 1MB)}},`
          @{Name=FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}}
        }

Here is what to do next.

  • I want my textbox to default to displaying my computer name
  • I want to add a trigger that when I click the Get-DiskInfo button, it should run the Get-DiskInfo function, using the computer name specified in the textbox
  • Finally, I want to take the objects I get from that and for each of them, add a new row to my ListView area

Change your code to the following, beginning on line 61 (or just below Get-FormVariables)

Function Get-DiskInfo {
param($computername =$env:COMPUTERNAME)
 
Get-WMIObject Win32_logicaldisk -ComputerName $computername | 
  Select-Object @{Name='ComputerName';Ex={$computername}},`
          @{Name=Drive Letter;Expression={$_.DeviceID}},`
          @{Name=Drive Label;Expression={$_.VolumeName}},`
          @{Name=Size(MB)’;Expression={[int]($_.Size / 1MB)}},`
          @{Name=FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}}
        }
 
$WPFtextBox.Text = $env:COMPUTERNAME
 
$WPFbutton.Add_Click({
Get-DiskInfo -computername $WPFtextBox.Text | % {$WPFlistView.AddChild($_)}
})

Let’s run it and see what happen’s when you click the button.

If it breaks in a new way, I call that progress

Layout almost there

Well, crap. Adding new rows worked, but now every column has the output for every property. This is happening because, very similar to when you work with the pipeline in PowerShell or make a function, you have to tell PowerShell how to bind to values.

To fix this, go up to your XAML for your GridView Columns and add a DisplayMemberBinding Property like this. Make sure if you’re deviating from the walkthrough and doing your own thing to pick names that make sense. If your name has a space in it, use single quotes around it.

https://gist.github.com/1RedOne/b206eb919f97f5df0036e2a6a4610b0d

And the finished product:

done

Whats up next?

Alright guys, I want to thank you for sticking with me to the end of this VERY long blog post. I hope you enjoy it and will use this technique to make some awesome GUIs of your own.

Join me for my post next time on this topic, part III in the GUI series, in which we dig into how to add some of the cooler and more difficult features to our GUI, like a tabbed interface (to get other WMI values) and how to use checkboxes and radio buttons, dropdown boxes and more!

Part III - Using Advanced GUI Elements in PowerShell

Final XAML Code here

https://gist.github.com/1RedOne/effdf04ad4c9b199bd4767e1e2fca68f

Full PowerShell Code here


#place your XAML below
$inputXML = @"
"@       
 
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N'  -replace '^<Win.*', '<Window'
 
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
#Read XAML
 
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged properties (PowerShell cannot process them)"
    throw}
 
#===========================================================================
# Load XAML Objects In PowerShell
#===========================================================================
 
$xaml.SelectNodes("//*[@Name]") | %{"trying item $($_.Name)";
    try {Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop}
    catch{throw}
    }
Function Get-FormVariables{
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true}
write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
 
Get-FormVariables
 
#===========================================================================
# Actually make the objects work
#===========================================================================
 
Function Get-DiskInfo {
param($computername =$env:COMPUTERNAME)
 
Get-WMIObject Win32_logicaldisk -ComputerName $computername | 
Select-Object @{Name='ComputerName';Ex={$computername}},`
              @{Name=Drive Letter;Expression={$_.DeviceID}},`
              @{Name=Drive Label;Expression={$_.VolumeName}},`
              @{Name=Size(MB)’;Expression={[int]($_.Size / 1MB)}},`
              @{Name=FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}}
            }
 
$WPFtextBox.Text = $env:COMPUTERNAME
 
$WPFbutton.Add_Click({
$WPFlistView.Items.Clear()
start-sleep -Milliseconds 840
Get-DiskInfo -computername $WPFtextBox.Text | % {$WPFlistView.AddChild($_)}
})
#Sample entry of how to add data to a field
 
#$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"})
 
#===========================================================================
# Shows the form
#===========================================================================
write-host "To show the form, run the following" -ForegroundColor Cyan
$Form.ShowDialog() | out-null

Link to the ISE Snippet

Continue Reading...

Part I - Creating PowerShell GUIs in Minutes using Visual Studio - A New Hope

April 10, 2015 FoxDeploy

Learning PowerShell GUIs

This post is part of the Learning GUI Toolmaking Series, here on FoxDeploy. Click the banner to return to the series jump page!


“I’ll never do it the old way again. EVER. “ -me

If you’ve been following my blog for any time, you’ll know that I love making tools using PowerShell.

I’ve written on the topic previously, and the approach I took then were based off of using the .net System.Windows.Forms class which really meant that you had to painfully, agonizingly create a GUI one element at a time.   I knew there were other methods, using something scary called XAML, and I’d even seen other’s tutorials, but frankly thought it looked too hard.

So I stuck with the pulling teeth method for years.  Until one day, I had some spare time and thought I’d take a look at the other GUI design method again…

I wish I had sooner, because it is so EASY!

What You’ll Need:

  • Visual Studio.  I’m using Community Tech Preview 2015, but you can do this with Visual Studio Express too. Get it here
  • A bit of helper PowerShell code, inspired by the awesome Chris Conte in this guest post on The Scripting Guy blog.  I’ll provide the snippet we need, when we need it.

Getting Visual Studio is easy, just download either the CTP edition or the trial of Ultimate.  There are ways of getting it for free too, via DreamSpark, MSDN, MVP Access or you can always use Express.  Assuming you’ve been able to install Visual Studio…

How this is going to work

A bit of history first on how this differs from what we did before.

Released with .net 3.0 back in 2006, Windows Presentation Foundation sought to rearchitect the way Windows Developers wrote their programs.  Instead of WinForm and the incredibly verbose style of configuring each element line by line, Microsoft brought a more civilized, CSS inspired design pattern to the world of Windows UX Design.

C# could be used as the code-behind (the actual payload of making things work), while the UI could be designed and themed in XAML, Microsoft’s new Extensible Application Markup Language format (inspired by XML), and the whole thing could easily be themed much like a website with CSS.

Since PowerShell is also based on .net, we can use Visual Studio to draw and design a GUI, then easily import it into PowerShell to use to make our own GUIS, but with our favorite scripting language used as the engine (instead of C#).  And the cool part is that anywhere that PowerShell runs and a GUI is available, our code will work there too!

(Sorry Server Core users of the World, Server Core does not have the WPF assemblies needed to run GUIs)

Interested?  Let’s begin!

Preparing Visual Studio

Start by launching Visual Studio

00-VisualStudio image

It’s so purple and creepy! Like a Gore Magala!

gore_magala

Not a Monster Hunter fan? Ok…moving on

You’ll want to click New Project on the left side of your screen

00-VisualStudio_newProject

Next, follow the numbers in the image below as you will 1. type WPF in the search box 2. click WPF Application in the main window, and then 3. customize the project name (if you’d like) and click OK.

00-VisualStudio_makeaWPF

This is what you’ll see, the fully blown Visual Studio UI.

VisualStudio

There is a lot of cruft we can disable though, so lets hide some elements.  For now, we don’t need Solution Explorer, which holds all of the files related to our project (since we won’t actually be publishing this project, just building its UI here) and we won’t need Properties until we add some items.

You can bring these items back by hitting F4, F6 or choosing them from the Alt-View menu up top You can bring these items back by hitting F4, F6 or choosing them from the Alt-View menu up top

Now, we will want to display and then pin the toolbox on the left side, to give us our goods we can drag and move around.

00-VisualStudio_PinStuff

Alright, there we go, much less noise, and now we’re able to get started.

Making a GUI in Visual Studio

The center area now holds our form.  You’re looking at what the GUI will be for your script or tool. You can resize the window, or drag and drop items into our form.

Two Tool making tips to remember

Before we dig in deeper, you should remember these tennants:

  • Give it a name - Every good tool needs a name, without a name, it’s harder to understand what something is for non-enthusiasts and you might end up with something being called ‘the Dan Script’ or ‘The Stephen Tool’
  • Wait till its ready - Always add an image and put a small amount of polish on your tools before you release them, or you’ll never get over that first bad impression

Let’s name this bad boy: click the title bar of your GUI, then hit F4 to show Properties

You could also just click the text and hit F2

You could also just click the text and hit F2

Let’s start with our GUI by adding an image first, then some text. The elements we want to grab from the tool box are Image, and a TextBlock object.

00-VisualStudio_toolbox_image

In the Toolbox, click Image, then draw the area you want the image to take on your form.

Now’s a good time to hit F4 to bring back some of the stuff we hid earlier, because we want to add some properties to this image (namely, the file source).

Go to explorer, find an image you like, and then copy its path and paste it here (pro-tip, don’t use quotes).

Put the file path in the Source field. Pro-tip: no quotes

Put the file path in the Source field. Pro-tip: no quotes

00-VisualStudio_toolbox_textblock

Now we’ll do the same again for a TextBlock.

I created mine right next to my MS-DOS FoxDeploy icon, and just added a bit of flavor text.

If you’re feeling really uninspired and just want to copy my text, then go ahead and enter the following:

“Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools “

If you really want to, you change the Font as well, by clicking in the Properties box on the right, and look towards the bottom for the Text area

Provided you’ve accomplished the above, you should now be looking at a pretty sweet field like this guy.

00-VisualStudio_lookingGood

But it doesn’t do anything yet…

One thing every good tool should have is a button!  Let’s add an OK button.  Go back to the toolbox, find the Button Control and drag and drop it.  I’ll put mine in the lower right hand corner.

A button shouldn’t say ‘Button’.  When you’ve clicked on the button, you can Hit F2 to quickly type in some new text for the face of the button, or edit this value in the properties as well.

00-VisualStudio_lookingGoodNowWithButton

I wasn’t satisfied with the miniscule font VS likes to use, so I decided to change it; you can change the size or font of pretty much anything by clicking it in the drafting pane, and then using the Properties window on the right (or Hit F4 to show it if properties is hiding) to change the Font Size.  I pumped the font up to 12 or 14.

I’d say if we add a text box (place where users can enter stuff) and a label (non-editable, it’s where we add a description to a field, we’ll have enough for now.  For both of these, go back to the toolbox and then grab one of each and draw them on your form.  I went agead and changed Label to say ‘UserName’, because I’ve started thinking about how to make this useful.

00-VisualStudio_done

 Coming Up…

In this guide, we installed Visual Studio and spent some time creating a WPF Powered GUI, which uses XAML as the language to describe the GUI.  All well and good, but how do we get this working in PowerShell??

Read on, intrepid reader, to find out.

Continue Reading...

Part III - DSC - Making our Domain Controller

April 03, 2015 FoxDeploy

IntroToDsc

So, it seems that I bit off a huge chunk here in making this a good walk-through, so in this section, we’ll be working again on our configuration from last week and, make this even better.

The Completed Script

What? We’re starting off with the full script?

Yep, I want you guys to have the working code from the beginning, and we’ll work through what we have to add to make this configuration our one-stop-shop for building a testlab.

$secpasswd = ConvertTo-SecureString 'IWouldLiketoRecoverPlease1!' -AsPlainText -Force
$SafeModePW = New-Object System.Management.Automation.PSCredential ('guest', $secpasswd)
 
$secpasswd = ConvertTo-SecureString 'IveGot$kills!' -AsPlainText -Force
$localuser = New-Object System.Management.Automation.PSCredential ('guest', $secpasswd)
 
 
configuration TestLab 
{ 
     param
    ( 
        [string[]]$NodeName ='localhost', 
        [Parameter(Mandatory)][string]$MachineName, 
        [Parameter(Mandatory)][string]$DomainName,
        [Parameter()]$firstDomainAdmin,
        [Parameter()][string]$UserName,
        [Parameter()]$SafeModePW,
        [Parameter()]$Password
    ) 
        
    #Import the required DSC Resources  
    Import-DscResource -Module xComputerManagement 
    Import-DscResource -Module xActiveDirectory 
   
    Node $NodeName
    { #ConfigurationBlock 
        xComputer NewNameAndWorkgroup 
        { 
            Name          = $MachineName
            WorkgroupName = 'TESTLAB'
             
        }
          
          
        User LocalAdmin {
            UserName = $UserName
            Description = 'Our new local admin'
            Ensure = 'Present'
            FullName = 'Stephen FoxDeploy'
            Password = $Password
            PasswordChangeRequired = $false
            PasswordNeverExpires = $true
            DependsOn = '[xComputer]NewNameAndWorkGroup'
        }
  
        Group AddToAdmin{
            GroupName='Administrators'
            DependsOn= '[User]LocalAdmin'
            Ensure= 'Present'
            MembersToInclude=$UserName
  
        }
 
        WindowsFeature ADDSInstall 
        { 
            DependsOn= '[Group]AddToAdmin'
            Ensure = 'Present'
            Name = 'AD-Domain-Services'
            IncludeAllSubFeature = $true
        }
         
        WindowsFeature RSATTools 
        { 
            DependsOn= '[WindowsFeature]ADDSInstall'
            Ensure = 'Present'
            Name = 'RSAT-AD-Tools'
            IncludeAllSubFeature = $true
        }  
 
        xADDomain SetupDomain {
            DomainAdministratorCredential= $firstDomainAdmin
            DomainName= $DomainName
            SafemodeAdministratorPassword= $SafeModePW
            DependsOn='[WindowsFeature]RSATTools'
            DomainNetbiosName = $DomainName.Split('.')[0]
        }
    #End Configuration Block    
    } 
}
 
$configData = 'a'
 
$configData = @{
                AllNodes = @(
                              @{
                                 NodeName = 'localhost';
                                 PSDscAllowPlainTextPassword = $true
                                    }
                    )
               }
 
 
TestLab -MachineName DSCDC01 -DomainName Fox.test -Password $localuser `
    -UserName 'FoxDeploy' -SafeModePW $SafeModePW `
    -firstDomainAdmin (Get-Credential -UserName 'FoxDeploy' -Message 'Specify Credentials for first domain admin') -ConfigurationData $configData
  
Start-DscConfiguration -ComputerName localhost -Wait -Force -Verbose -path .\TestLab -Debug

What’s changed?

Warning: Laziness has occurred. I got sick of typing in Passwords over and over, so I’m using the ConvertTo-SecureString cmdlet to save on typing in the first four lines of the script. Update these values to whatever you’d like for your own testlab domain’s Recovery Password and local admin account password.

In order to progress this DSC Configuration from making just a local user, making him an admin, and changing the name, we have to add some extra configuration resources to our config. From the top down, these are the one’s we’ll be making use of.

• WindowsFeature - Install the Windows Feature AD-Domain Services, then RSAT-AD-TOOLS • xADDomain - Creates a domain if one doesn’t exist and makes this a Domain Controller

We’ll also need to add some new parameters as well,

  [Parameter(Mandatory)][string]$DomainName,
  [Parameter()]$firstDomainAdmin,
  [Parameter()]$SafeModePW,

These parameters are used to configure the following values:

• DomainName - for instance “foxdeploy.com” • FirstDomainAdmin - creates a user as a domain admin • SafeModePW - this is the Active Directory Recovery Mode password

In order to use xADDomain, we have to import the xActiveDirectory module, so if you haven’t downloaded that yet, get it here as a part of the DSC Wave Resource Kit.

. To make use of this resource, we also have to import it.

Import-DscResource -Module xActiveDirectory 

The major tasks we’re accomplishing here are Installing the Windows Features of AD Domain Services, the AD Server Administration tools like ADUC, DHCP and DNS consoles, and finally making an AD Domain.

Note: You may have noticed the presence of a DependsOn section for every resource used thus far.  This is only necessary if the application order matters for your configurations.  As it turns out, pretty much every item I’ve used up to this point needs to be applied in a particular order (We have to have a user before we can add it to a group, we must have a Domain before we can promote our server to a domain controller, and we must have the Windows Features for a DC before we can run dcpromo), however, you won’t always need to add DependsOn unless your Configuration really needs it.

Digging into the Resources

Configuring the server to add Domain Services and the RSAT tools is pretty easy, all that we have to do is add two WindowsFeature resources, specifying the Name of the feature needed (get the feature from Get-WindowsFeature, and look at the Name property). We specify Ensure = Present, and IncludeAllSubFeature to get all of the juicy bits.

WindowsFeature ADDSInstall 
    { 
        DependsOn= '[Group]AddToAdmin'
        Ensure = 'Present'
        Name = 'AD-Domain-Services'
        IncludeAllSubFeature = $true
    }
      
    WindowsFeature RSATTools 
    { 
        DependsOn= '[WindowsFeature]ADDSInstall'
        Ensure = 'Present'
        Name = 'RSAT-AD-Tools'
        IncludeAllSubFeature = $true
    }  

Suprisingly, it is also very easy to configure our domain using the xADDomain resource. These are seriously the only values we have to provide. Running through the values we’re configuring:

• DomainAdministratorCredential - Our first additional domain admin creds • DomainName - the unique name for our new domain • SafeModeAdminPassword - the password you’ll use to recover your domain on the dark day when you nuked the domain • DomainNetbiosName - we can actually derive this from the -DomainName the user provides

xADDomain SetupDomain {
    DomainAdministratorCredential= $firstDomainAdmin
    DomainName= $DomainName
    SafemodeAdministratorPassword= $SafeModePW
    DomainNetbiosName = $DomainName.Split('.')[0]
    DependsOn='[WindowsFeature]RSATTools'
}

Applying the Configuration

As before, applying the configuration is just as simple as loading the config into memory then running it like a cmdlet. Finally, we invoke the configuration using Start-DSCConfig

 $cred = (Get-Credential -UserName 'FoxDeploy' -Message 'Specify Credentials for first domain admin')

 TestLab -MachineName DSCDC01 -DomainName Fox.test `
  -Password $localuser -UserName 'FoxDeploy' `
  -SafeModePW $SafeModePW  -firstDomainAdmin $cred `
  -ConfigurationData $configData
  
Start-DscConfiguration -ComputerName localhost -Wait -Force -Verbose -path .\TestLab -Debug

I’ve talked through this enough, now time for some pretty pictures!

Checking to see if Active Directory Domain Services is installed Checking to see if Active Directory Domain Services is installed

Making sure our system is a domain controller Making sure our system is a domain controller

Cool juicy bits about Domain Services being installed... Cool juicy bits about Domain Services being installed…

DNS and DHCP...online! DNS and DHCP…online!

Pre-reboot, our domain settings are listed in server manager Pre-reboot, our domain settings are listed in server manager

After a reboot, Server Manager shows DHCP, DNS, and AD DS all healthy and online After a reboot, Server Manager shows DHCP, DNS, and AD DS all healthy and online

RSAT Tools are ready and loaded! RSAT Tools are ready and loaded!

What’s next?

We have a one-click working domain controller config now, but starting with our next post in this series, we’ll configure DNS and DHCP for this Domain Controller, so that our testlab DC will publish itself to DNS and be able to give out DHCP address to guest VMs in our lab.

Continue Reading...

Part II - DSC - Joining our user to the Local Administrators Group

March 31, 2015 FoxDeploy

IntroToDsc

This post is part of the Learning PowerShell DSC Series, here on FoxDeploy. Click the banner to return to the series jump page!

This three part series is going to be about twenty parts at the rate I keep forgetting things.

We left off in our last episode with creating our local user, we’ll build on last week’s config by adding our user to the admin group, to kick things off.

By now the process should be familiar:

  • Run Get-DSCResource to see which configuration items are available
  • Be lazy and run Get-DscResource Group select -expand Properties Select -expand Name ForEach { “$_=`”`”” } to get our copy-paste output of the items we need to configure for that resource
  • Copy and paste this into our Configuration Block, below our previous item

Being that we want to configure Groups, we’ll search for Groups…

Get-DscResource Group | select -expand Properties | Select -expand Name |  ForEach { '$_=`'`'' } 
GroupName=''
Credential=''
DependsOn=''
Description=''
Ensure=''
Members=''
MembersToExclude=''
MembersToInclude=''

And we see all of the items for the Group Resource. We’ll lazily copy and paste this as a new configuration item, to add that at the bottom of our Node $NodeName {#confirguration block}

Now, here are the values you’ll want to include.

Group AddToAdmin{
             #we want to add the user to the built-in Admin group
            GroupName='Administrators'   
            #we want this to execute after the user is created
            DependsOn= '[User]LocalAdmin' 
            #the other alternative is Absent, which would remove this user
            Ensure= 'Present'
            #we can reuse the same value for our User creation config,
            MembersToInclude=$UserName

So, we’ve updated our Configuration. Now we select the whole configuration to reload it into memory by highlighting the whole thing and hitting F8.

Pro-tip, click a line or highlight many lines and hit F8 to execute just that part of your script

Alright, let’s invoke it…

TestLab -MachineName DSCDC01 -WorkGroupName TESTLAB `
  -Password (Get-Credential -UserName 'FoxDeploy' -Message 'Enter New Password') `
  -UserName 'FoxDeploy' -ConfigurationData $configData
 
Start-DscConfiguration -ComputerName localhost -Wait -Force -Verbose -path .\TestLab

And the results

[[xComputer]NewNameAndWorkgroup] Checking if computer name is DSCDC01
[[xComputer]NewNameAndWorkgroup] Checking if workgroup name is TESTLAB
[[xComputer]NewNameAndWorkgroup]  in 0.2180 seconds.
[[xComputer]NewNameAndWorkgroup]
[[xComputer]NewNameAndWorkgroup]
[[User]LocalAdmin]
[[User]LocalAdmin]
[[User]LocalAdmin] A user with the name FoxDeploy exists.
[[User]LocalAdmin] The value of the Password property does not match.
[[User]LocalAdmin]  in 2.5350 seconds.
[[User]LocalAdmin]
[[User]LocalAdmin] Configuration of user FoxDeploy started.
[[User]LocalAdmin] Performing the operation 'Set' on target 'User: FoxDeploy'.
[[User]LocalAdmin] User FoxDeploy properties updated successfully.
[[User]LocalAdmin] Configuration of user FoxDeploy completed successfully.
[[User]LocalAdmin]  in 2.4330 seconds.
[[User]LocalAdmin]
[[Group]AddToAdmin]
[[Group]AddToAdmin]
[[Group]AddToAdmin] A group with the name Administrators exists.
[[Group]AddToAdmin] Resolving Administrator as a local account.
[[Group]AddToAdmin] Resolving foxdeploy as a local account.
[[Group]AddToAdmin] At least one member FoxDeploy of the provided MembersToInclude parameter does not have a match in the existing grou
p Administrators.
[[Group]AddToAdmin]  in 2.7040 seconds.
[[Group]AddToAdmin]
[[Group]AddToAdmin] Performing the operation 'Set' on target 'Group: Administrators'.
[[Group]AddToAdmin] Resolving foxdeploy as a local account.
[[Group]AddToAdmin] Group Administrators properties updated successfully.
[[Group]AddToAdmin]  in 2.4810 seconds.

And our new user is now in the local admins group too!](../assets/images/2015/03/images/user_go_1.png) And our new user is now in the local admins group too!

Man, this is so much fun, I love it.

Join us next post as we escalate this machine to make it a Domain Controller!

The Full code

configuration TestLab 
{ 
    param
    ( 
        [string[]]$NodeName ='localhost', 
        [Parameter(Mandatory)][string]$MachineName, 
        [Parameter(Mandatory)][string]$WorkGroupName,
        [Parameter()][string]$UserName,
        [Parameter()]$Password
    ) 
        
    #Import the required DSC Resources  
    Import-DscResource -Module xComputerManagement 
   
    Node $NodeName
    { #ConfigurationBlock 
        xComputer NewNameAndWorkgroup 
        { 
            Name          = $MachineName
            WorkGroupName = $WorkGroupName
        }
         
         
        User LocalAdmin {
            UserName = $UserName
            Description = 'Our new local admin'
            Ensure = 'Present'
            FullName = 'Stephen FoxDeploy'
            Password = $Password
            PasswordChangeRequired = $false
            PasswordNeverExpires = $true
            DependsOn = '[xComputer]NewNameAndWorkGroup'
        }
 
        Group AddToAdmin{
            GroupName='Administrators'
            DependsOn= '[User]LocalAdmin'
            Ensure= 'Present'
            MembersToInclude=$UserName
 
        }
    #End Configuration Block    
    } 
}
 
$configData = 'a'
 
$configData = @{
                AllNodes = @(
                              @{
                                 NodeName = 'localhost';
                                 PSDscAllowPlainTextPassword = $true
                                    }
                    )
               }
 
#See whats needs to be configured
# Get-DscResource User | select -ExpandProperty Properties | select -expand name
 
TestLab -MachineName DSCDC01 -WorkGroupName TESTLAB -Password (Get-Credential -UserName 'FoxDeploy' -Message 'Enter New Password') -UserName 'FoxDeploy' -ConfigurationData $configData
 
Start-DscConfiguration -ComputerName localhost -Wait -Force -Verbose -path .\TestLab
Continue Reading...

Orchestrator: Solved 'Cannot invoke this function because current host does not implement it'

March 30, 2015 FoxDeploy

This is a very frustrating error in SCORCH, Opalis, Orchestrator, whatever you want to call it. Bring on SMA because I’ve had enough!

Symptom

When running a PowerShell Script or an Exchange Administrative PowerShell Command in PowerShell, the activity will fail with:

‘Cannot invoke this function because the current host does not implement it.’

Cause

The reason for this is that the command you’re trying to run is trying to send confirmation back to the shell (end-user) to provide Confirmation before enacting a change.  The Orchestrator host doesn’t have any mechanism to prompt for change, and thus the message we see.

As it turns out, the error message really was trying to help us, but just incredibly poorly written.

Resolution

There is a quick fix available for this, add either -Confirm:$false or -Force to your cmdlet, based on the command you’re using.

Suggestion: replace this message with ‘This cmd requires user feedback, and cannot be automated in it’s current form. Try reading the Get-Help page for the cmdlet used, and consider adding -Force or -Confirm:$false if your cmdlet requires it’.

Continue Reading...

Part I.5: Creating a user for our Testlab with DSC

March 26, 2015 FoxDeploy

Learning DSC Series

Being that I am a creature of pure distilled vanity, I often reread my articles and blog posts after the fact to make sure that I wrote well and don’t have any typos. Well, when I reread my last post, I realized that I forgot to add the step of making a User Account with PowerShell DSC! Whoops!

As it turns out, actually creating a user with DSC had me ready to pull my hair off in frustration, so to save you intrepid Googlers from this pain, I’ve outlined what you SHOULDN’T do, and also have a working easy to understand demo in the bottom.

Add a User Resource to our DSC Config

So, building on our DSC Config from last week; when we want to add a new DSC Resource, the first start is to run Get-DSCResource and look at what Properties we can (and need to) focus on.

Get-DscResource User | select -ExpandProperty Properties

newUser_01

Based on what we see above, we should be good to add the following under xComputer

User LocalAdmin {
    UserName = $UserName
    DependsOn = '[xComputer]NewNameAndWorkGroup'
    Description = 'Our new local admin'
    Disabled = $false
    Ensure = 'Present'
    FullName = 'Stephen FoxDeploy'
    Password = '$Password'
    PasswordChangeRequired = $false
    PasswordNeverExpires = $true
 
       }

You can see that we added a few variables there, so let’s go add them all to our Parameter block.

Change our params block in this script to the following

param
    (
      [string[]]$NodeName ='localhost',
      [Parameter(Mandatory)][string]$MachineName,
      [Parameter(Mandatory)][string]$WorkGroupName,
      [Parameter()][string]$UserName,
      [Parameter()][string]$Password
    )

Now let’s invoke it…

TestLab -MachineName DSCDC01 `
   -WorkGroupName TESTLAB `
   -Password 'myRootPw' `
   -UserName 'Stephen FoxDeploy'

ERROR :(

Write-NodeMOFFile : Invalid MOF definition for node 'localhost': Exception calling 'ValidateInstanceText' with '1' argument(s): 'Convert property 'Password' value from type 'STRING' to type 'INSTANCE' failed At line:37, char:2 Buffer: onName = 'TestLab'; };^ 

Fix: Convert property value from type string to type instance failed

What?!? Maybe my brain just doesn’t work so good, but I had a really hard time understanding what this message meant: Convert Property Value from type String to type Instance. So I took a peek at TechNet.

Checking TechNet, I see this example of how to use the DSC User Resource(I’ve actually bolded the part that was causing me grief, let’s see if you can figure it out quicker than I could!):

```User UserExample { Ensure = “Present” “Absent” UserName = “SomeName” Password = $passwordCred DependsOn = “[Group]GroupExample” }


Let's look at the error message again…

"Convert property 'Password' value from type 'STRING' to type 'INSTANCE' failed

Hmm…If converting from a string failed, and TechNet says it needs to be a Credential object instead…! Wait! PowerShell is telling me I can't give the PW as a string, it actually has to be a credential object! Not sure why it took me so long to understand this. Actually, if we take a look at the output from Get-DSCResource, it even says right there what type to provide the data in.

![newUser_015](../assets/images/2015/03/images/newuser_015.png?w=705)

So, let's replace our -PassWord 'RootPW' with a parenthesis with Get-Credential and see what happens...

```powershell
$cred = (Get-Credential -UserName 'FoxDeploy' -Message 'Enter New Password')

TestLab -MachineName DSCDC01 `
  -WorkGroupName TESTLAB `
  -Password $cred `
  -UserName 'FoxDeploy'

Running the cmd above gives us…some more blood in the water, but this is great because we’ve solved the first problem!

ConvertTo-MOFInstance : System.InvalidOperationException error processing property 'Password' OF TYPE 'User': Converting and storing encrypted passwords as plain text is not recommended. For more information on securing credentials in MOF file, please refer to MSDN blog: http://go.microsoft.com/fwlink/?LinkId=393729 

Hey, at least we’re getting somewhere! This is probably one of the most informative error messages I’ve ever had in PowerShell! It’s saying hey, you can’t do this, here is a link with more info. I like it! Much better than before.

I did some googling and found this article on the topic, it turns out we need to specify a -ConfigurationData parameter, which will allow us to tell PS to, just this once, ignore our bad behavior password.

Let’s throw this bad boy on there…

$configData = @{
  AllNodes = @(
  @{
    NodeName = 'localhost';
    PSDscAllowPlainTextPassword = $true
  }
  )
}

Now, to run it, one last time!

newUser_02

BLAM! Let’s apply it!

RESULTS

[[xComputer]NewNameAndWorkgroup] Checking if computer name is DSCDC01
[[xComputer]NewNameAndWorkgroup] Checking if workgroup name is TESTLAB
[[xComputer]NewNameAndWorkgroup]  in 0.2420 seconds.
[[xComputer]NewNameAndWorkgroup]
[[User]LocalAdmin] A user with the name FoxDeploy does not exist.
[[User]LocalAdmin]  in 2.4380 seconds.
[[User]LocalAdmin]
[[User]LocalAdmin] Configuration of user FoxDeploy started.
[[User]LocalAdmin] Performing the operation 'Add' on target 'User: FoxDeploy'.
[[User]LocalAdmin] User FoxDeploy created successfully.
[[User]LocalAdmin] Configuration of user FoxDeploy completed successfully.
[[User]LocalAdmin]  in 2.5090 seconds.

Now, let’s look to see if our User was created

newUser_03

Ok, that’s damned sexy

Completed DSC File

This DSC config will rename our PC, join it to a workgroup, and then add a new local user to the machine. When you run this, you’ll be prompted to provide the PW for the new local user. Make sure to change the -Username value to a name you’d like to use.

cconfiguration TestLab
{
    param
    (
        [string[]]$NodeName ='localhost',
        [Parameter(Mandatory)][string]$MachineName,
        [Parameter(Mandatory)][string]$WorkGroupName,
        [Parameter()][string]$UserName,
        [Parameter()]$Password
    ) 
 
    #Import the required DSC Resources
    Import-DscResource -Module xComputerManagement 
 
    Node $NodeName
    {
        xComputer NewNameAndWorkgroup
        {
            Name          = $MachineName
            WorkGroupName = $WorkGroupName
        }
 
        User LocalAdmin {
            UserName = $UserName
            Description = 'Our new local admin'
            Ensure = 'Present'
            FullName = 'Stephen FoxDeploy'
            Password = $Password
            PasswordChangeRequired = $false
            PasswordNeverExpires = $true
            DependsOn = '[xComputer]NewNameAndWorkGroup'
        }
 
    }
}
 
$configData = 'a'
 
$configData = @{
                AllNodes = @(
                              @{
                                 NodeName = 'localhost';
                                 PSDscAllowPlainTextPassword = $true
                                    }
                    )
               }
 
#See whats needs to be configured
# Get-DscResource User | select -ExpandProperty Properties | select -expand name
 
TestLab -MachineName DSCDC01 -WorkGroupName TESTLAB -Password (Get-Credential -UserName 'FoxDeploy' -Message 'Enter New Password') -UserName 'FoxDeploy' -ConfigurationData $configData
 
Start-DscConfiguration -ComputerName localhost -Wait -Force -Verbose -path .TestLab

Wrapping Up

So, it seems that most things in DSC look deceptively easy when you see the finished result, but working up to that is quite difficult. Instead this was more of a deep dive into using the User Resource with DSC. Join us again next week when we add our user to the Administators built-in Group, then make our machine into a Domain Controller.

Continue Reading...


Microsoft MVP

Five time Microsoft MVP, and now I work for the mothership


Need Help?

Get help much faster on our new dedicated Subreddit!

depicts a crowd of people in a night club with colored lights and says 'join the foxdeploy subrreddit today'


Blog Series
series_sml_IntroToDsc
series_sml_PowerShellGUI series_sml_IntroToRaspberryPi Programming series_sml_IntroToWindows Remote Management Series The Logo for System Center Configuration Manager is displayed here Depicts a road sign saying 'Learning PowerShell Autocomplete'




Blog Stats