Quantcast
Channel: FoxDeploy – FoxDeploy.com
Viewing all 109 articles
Browse latest View live

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

$
0
0

IntroToDsc

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

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 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!
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



Part III – DSC – Making our Domain Controller

$
0
0

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

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

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.


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

$
0
0

series_PowerShellGUI

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 tool 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’d seen other’s tutorials, but frankly thought it looked too hard.

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 ChrisConte 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…

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, you will type WPF in the search box, click WPF, then 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.  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 fils 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

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:

  • 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’
  • 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).

When you first create this on your form, it will be

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_textblockNow we’ll do the same again for a TextBlock.

I created mine right new 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

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
This font is so small!

Oh yeah, you can change the size or font of pretty much anything by clicking it, looking at the Properties window on the right (or Hit F4 to show it if properties is hiding).  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…

We’ve installed Visual Studio, and have now experimented around with drawing a simple GUI.  In our next installment, we’ll go through the very few steps needed to import this into PowerShell, and I’ll show you how to read data from this form, and also publish data to a form too!


Part II – Deploying PowerShell GUIs in Minutes using Visual Studio

$
0
0

series_PowerShellGUI

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 play around with my example, just copy this stuff

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication2"
        mc:Ignorable="d"
        Title="FoxDeploy Awesome Tool" Height="345.992" Width="530.344" Topmost="True">
    <Grid Margin="0,0,45,0">
        <Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/>
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer>
        		<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/>
        	</InlineUIContainer></TextBlock>
        <Button x:Name="button" Content="OK" HorizontalAlignment="Left" Height="55" Margin="370,235,0,0" VerticalAlignment="Top" Width="102" FontSize="18.667"/>
        <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="221,166,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/>
        <Label x:Name="label" Content="UserName" HorizontalAlignment="Left" Height="46" Margin="56,162,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/>

    </Grid>
</Window>

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

#ERASE ALL THIS AND PUT XAML BELOW between the @" "@ 
$inputXML = @"
<Window x:Class="Azure.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Azure"
        mc:Ignorable="d"
        
        Title="iVision Azure Accelerator" Height="524.256" Width="332.076">
    <Grid Margin="0,0,174,0">
    </Grid>
</Window>
"@        

$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-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}

#===========================================================================
# Load XAML Objects In PowerShell
#===========================================================================

$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}

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
#===========================================================================


#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'

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!
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

$btnOK.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:

#ERASE ALL THIS AND PUT XAML BELOW between the @" "@ 
$inputXML = @"
<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication2"
        mc:Ignorable="d"
        Title="FoxDeploy Awesome Tool" Height="416.794" Width="598.474" Topmost="True">
    <Grid Margin="0,0,45,0">
        <Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/>
        <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer>
        		<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/>
        	</InlineUIContainer></TextBlock>
        <Button x:Name="button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="35" Margin="393,144,0,0" VerticalAlignment="Top" Width="121" FontSize="18.667"/>
        <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="186,144,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/>
        <Label x:Name="label" Content="ComputerName" HorizontalAlignment="Left" Height="46" Margin="24,144,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/>
        <ListView x:Name="listView" HorizontalAlignment="Left" Height="156" Margin="24,195,0,0" VerticalAlignment="Top" Width="511" FontSize="16">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Drive Letter" Width="120"/>
                    <GridViewColumn Header="Drive Label" Width="120"/>
                    <GridViewColumn Header="Size(MB)" Width="120"/>
                    <GridViewColumn Header="FreeSpace%" Width="120"/>
                </GridView>
            </ListView.View>
        </ListView>


    </Grid>
</Window>

"@        

$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-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}

#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================

$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}

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
#===========================================================================

$WPFbutton.Add_Click({
$userInput = $WPFtextBox.Text
$form.Close()
})
#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'


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.

    <GridView>
                        <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="120"/>
                        <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="120"/>
                        <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="120"/>
                        <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="120"/>
                    </GridView>
    
    

    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!

    Final XAML Code here

    <Window x:Class="WpfApplication2.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApplication2"
            mc:Ignorable="d"
            Title="FoxDeploy Awesome Tool" Height="416.794" Width="598.474" Topmost="True">
        <Grid Margin="0,0,45,0">
            <Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/>
            <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer>
            		<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/>
            	</InlineUIContainer></TextBlock>
            <Button x:Name="button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="35" Margin="393,144,0,0" VerticalAlignment="Top" Width="121" FontSize="18.667"/>
            <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="186,144,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/>
            <Label x:Name="label" Content="ComputerName" HorizontalAlignment="Left" Height="46" Margin="24,144,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/>
            <ListView x:Name="listView" HorizontalAlignment="Left" Height="156" Margin="24,195,0,0" VerticalAlignment="Top" Width="511" FontSize="16">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="120"/>
                        <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="120"/>
                        <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="120"/>
                        <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="120"/>
                    </GridView>
                </ListView.View>
            </ListView>
    
    
        </Grid>
    </Window>
    

    Full PowerShell Code here

    #ERASE ALL THIS AND PUT XAML BELOW between the @" "@ 
    $inputXML = @"
    <Window x:Class="WpfApplication2.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApplication2"
            mc:Ignorable="d"
            Title="FoxDeploy Awesome Tool" Height="416.794" Width="598.474" Topmost="True">
        <Grid Margin="0,0,45,0">
            <Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/>
            <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer>
            		<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/>
            	</InlineUIContainer></TextBlock>
            <Button x:Name="button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="35" Margin="393,144,0,0" VerticalAlignment="Top" Width="121" FontSize="18.667"/>
            <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="186,144,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/>
            <Label x:Name="label" Content="ComputerName" HorizontalAlignment="Left" Height="46" Margin="24,144,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/>
            <ListView x:Name="listView" HorizontalAlignment="Left" Height="156" Margin="24,195,0,0" VerticalAlignment="Top" Width="511" FontSize="16">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="120"/>
                        <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="120"/>
                        <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="120"/>
                        <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="120"/>
                    </GridView>
                </ListView.View>
            </ListView>
    
    
        </Grid>
    </Window>
    
    "@        
    
    $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-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
    
    #===========================================================================
    # Store Form Objects In PowerShell
    #===========================================================================
    
    $xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
    
    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


    Windows 10 for Raspberry Pi – Solving DISM ‘the Drive can’t find the sector requested’

    $
    0
    0

    series_RaspberryPi

    This is part of the Learning Raspberry Pi Series here on FoxDeploy.com. Click the banner for more Raspberry Pi and Windows!


    Symptom

    You’re like me, and super excited to start playing with Windows 10 for the Internet of Things (IoT) on your Rasperry Pi 2.  But when running the DISM commands from this post, you see the following error:

    The drive cannot find the sector requested
    The drive cannot find the sector requested

    Reason

    This is sort of a very opaque error, and in this case, double-check that you’re using a big enough SD card.  As it turns out, I accidentally picked up a 4 GB card, which is too small!  I think the specific cause of this error comes from the fact that the image in question won’t actually fit on a card smaller than 8 GB, and thus the Deployment Image Servicing Management tool craps out trying to write to that sector.

    Solution

    Buy a bigger SD Card!  Here’s one which will work perfectly!  When you’re buying a Micro SD card, don’t cheap out.  The quality matters and the class (the bin rate) of the card definitely matters.  Smaller cards are virtually identical to the larger capacity SD cards and the only difference is physical imperfections in the card, which can mean an earlier fail rate and other problems.

     

     


    Super-Fast walkthrough: running Win10 Preview on Raspberry Pi 2 and what’s it like

    $
    0
    0

    series_RaspberryPi

    This is part of the Learning Raspberry Pi Series here on FoxDeploy.com. Click the banner for more Raspberry Pi and Windows!


    If you’re like me, you got really excited to hear about the possibilities of running Windows 10 IoT preview on your Raspberry Pi 2.  I actually bought one just for a blog series on PowerShell and Raspberry Pi, so stay tuned for more!  Note: I did all of this while drinking some brewskies the other night.  If I can do it mildly intoxicated, then you can definitely do it sober.

    What you’ll need

    • Laptop / Desktop running Windows 10 Preview, can’t be a VM as you’ll need hardware access
    • RaspBerry Pi 2:
      • Base model
      • Fancy Smancy kit – I bought this one.  You’ll really want to put a heat sink on the CPU and GPU, this thing gets super hot.  This kit includes it.
    • 8 GB or higher Class 10 Micro SD Card.  Don’t buy an off-brand or you’re begging for pain!
    • Sign-up for the Windows Connect Program here and click through all of the EULAs to enable the download tab.  Download the .zip file and unzip it.
    • Optional : SD Card reader if you don’t have one

    How to get it up and running

    1. Download the .ffu file listed above.
    2. Go to your Win 10 preview machine, use DiskPart to figure out which disk your SD Card is.  This will likely be the smallest disk when you run diskpart – List Disk .  Grab the disk number.

      List disk
      Whoa Stephen, 64 GB, you must be a baller. That’s right, I make literally upwards of $8 an hour.

    3. Change into the directory where you put your .ffu file
    4. Run this cmd
      dism.exe /Apply-Image /ImageFile:flash.ffu /ApplyDrive:\\.\PhysicalDriveN /SkipPlatformCheck
    5. When you see 100% you’re done!  Put the SD Card in your Raspberry Pi 2 and boot her up.

    What’s it like?

    Plug in, connect network and boot.  Done.  It takes forever to boot, like five mins.  You’ll first see a random Blue debugging screen with some clickable elements that don’t actually work.
    You can click these on the left, 'App' and 'MiraCast/View' but they don't actually do anything
    You can click these on the left, ‘App’ and ‘MiraCast/View’ but they don’t actually do anything
    You can ignore that, and then the system will reboot, showing you the cool new logo of Windows 10 IoT.
    Man, FoxDeploy, this is a terrible screen shot.  Were you drunk taking a picture of your TV?  Uh...yes.  Yes I was.
    Man, FoxDeploy, this is a terrible screen shot. Were you drunk taking a picture of your TV? Uh…yes. Yes I was.
    Eventually you’ll end up on a page like this, showing you the IP to manage this bad boy.

    There is a program you can also find in the .zip from earlier, which runs only on Windows 10 PCs.  It looks like this.
    IoTwatcher_NoContextMenu
    You can right-click items here to jump into a network Share, or open a Web browser.
    IoTwatcher
    You can open that IP in your browser for a very useful web management tool.
    If you know how to make AppX packages, you can directly push them using the Web console, from this screen.

    Inline image 1

     You can also check process performance, which shows you a very sexy looking performance graph viewer
    How'd you put load on the Pi?  By launching a PowerShell session.
    How’d you put load on the Pi? By launching a PowerShell session.
    Remote PowerShell works, just add the PC to your trustedHosts like using the WSman Cmdlets, and if you’re remoting from Win10, disable the PS ReadLine module (it will cause errors).  For more info, follow the guide here.
    Remoting performance to the Reaspberry Pi is very, very slow.  VERY slow.  But it’s a work in progress.
    My transcript of how I connected:
    set-Item WSMan:\localhost\Client\TrustedHosts 10.63.30.54
    
    WinRM Security Configuration.
    This command modifies the TrustedHosts list for the WinRM client. The computers in the TrustedHosts list might not
    be authenticated. The client might send credential information to these computers. Are you sure that you want to
    modify this list?
    [Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): y
    PS C:\WINDOWS\system32> get-Item WSMan:\localhost\Client\TrustedHosts
      WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client
      Type            Name                           SourceOfValue   Value
      ----            ----                           -------------   -----
      System.String   TrustedHosts                                   10.63.30.54
    
    PS C:\WINDOWS\system32> Enter-PSSession -ComputerName 10.63.30.54 -Credential 10.63.30.54\Administrator
    [10.63.30.54]: PS C:\Users\Administrator\Documents>
    
    Digging into the covers, you’ll see some weirdness…like no MSI class or Win32_Product classes :(
    NoMSIClass

    What’s next?

    Well, from here on in, we’ll try to find some cool stuff on the Raspberry Pi.  My next to-do’s include finding temperature sensors, if they exist, and finding some other cool stuff we can run on the Pi from remote PowerShell.


    Quick How-To: Add an image to your Raspberry Pi Windows App

    $
    0
    0

    series_RaspberryPi

    This is part of the Learning Raspberry Pi Series here on FoxDeploy.com. Click the banner for more Raspberry Pi and Windows!


    One of the first things you’ll want to do when you make a GUI to push out to your Raspberry Pi 2 with Windows 10 is to make a fancy smancy GUI. To do that, you’ll need an image!

    Assuming you’ve followed the guide here to make your first HelloWorld app, you might want to add an image. This will be your first image embedded in a functional app, so you’d better make it a good one!

    For me, the pursuit of a fine image is half of the fun. Most of the fun! I like to Google around for the best image, that perfect piece of clipart which accurately describes my project, inspires my end user and aligns our paradigms…and then discard it and pick a picture of a Fox or a Sky Bison (my wife and I are really enjoying Avatar the Last Airbender now!)

    Now that I’ve got a folder of some high quality jpegs ready

    high Quality Jpegs

    Using the toolbox image control, you can draw the outline for an image to put it wherever you’d like and then you’ll get a…uh…

    UglyX
    Wait, it’s an X, what gives?

    Previously, we were using PowerShell implementing .net classes to draw WPF forms, meaning we could put together a GUI in real time using bits and pieces from all over our system, including files stored in relative paths.

    Since we’re not dealing with the same sorts of apps we worked with before, we can’t just point to outside files anymore. We’re talking about compiled code now; we’ve gotta up our game, and that means including assets.

    Give me the short answer

    Fine, I’ll save you a ton of words here. If you want to include an image in your program, you’ll have to embed it as an asset. These things get embedded in your .exe/code when you compile and are the one true way to deliver resources within your code. It is possible to download your image when the tool runs…which is something I’ll cover later.

    To embed an image, follow these instructions/gifs

    Open Solution Explorer and click on the Assets Folder. Now, right click->Add->Add Existing Item.

    Add Item
    Pick your file.

    Now click your image placeholder again and check out the dropdown box.

    Awesome!

    Success! Our image was included!

    You’re now on your way to building beautiful and functional Windows 10 Raspberry Pi apps, like this one:

    Uh...this doesn't look very functional Stephen
    Uh…this doesn’t look very functional Stephen

    Uh…that doesn’t look very functional Stephen…


    Part III – Using Advanced GUI Elements in PowerShell

    $
    0
    0

    series_PowerShellGUI

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


    Welcome back to the GUI Series here on FoxDeploy.com! In the previous weeks, I’ve had a lot of feedback and requests from you guys (which I absolutely love! Assuming I don’t need sleep, I’m content to stay up till the wee hours of the morning helping you guys out and responding to your comments or requests, so keep ’em coming!). As for where this blog is heading…I’ve really been bit hard by the maker bug, and I recently purchased a RaspBerry Pi 2, which is super awesome and incredibly powerful. Direct deploying a program from Visual Studio to this little guy and seeing it light up and project content to my HDMI monitor really makes it feel like I’m doing something tangible. I’ll be making a whole series of posts about cool things you can do with the Pi. If you have any ideas, send ’em over!

    We’re going to cover a number of topics in this one, namely ‘how do I do ex’ questions. We’ll have two somewhat silly examples to start, followed by an actually useful GUI to create a user to wrap us up here.

    We’ll specifically be hitting all of these guys here, in order.

    • Hide a UI Element (by special request)
    • Use Checkboxes
    • Use radio buttons (by special request)
    • Populate a drop-down box automatically

    Also, in a big departure from the past, I’ll not be posting full code on here anymore. From now on, code samples from here will always be linked and kept up-to-date on GitHub. You can find the code from this post here: https://github.com/1RedOne/Post_III

    Alright, let’s get started. Power up Visual Studio and create a new project, specify ‘WPF’ as your type in the search box.

    Hide a UI element

    We’ll draw an image and set it to invisible, and then add a button that will make it appear or disappear. You can imagine and endless amount of examples where this could be useful

    Now, let’s add a meaningful image (read:skybison) and then make it hidden. Finally, let’s add a button.

    The XAML

    <Window x:Class="BlogPostIII.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:BlogPostIII"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid x:Name="background" Background="#FF1D3245">
            <Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="171,154,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\sred13\Dropbox\My Code\Migmon\htdocs\Appa.png" Visibility="Hidden" />
            <Button x:Name="button" Content="Reveal Hidden Skybisons" HorizontalAlignment="Left" Height="34" Margin="10,277,0,0" VerticalAlignment="Top" Width="155"/>
    
        </Grid>
    </Window>
     
    

    This will give us a form with an image, and a button. To hook the button up to the image, we just need to add a few snippets of code:

    $WPFbutton.Add_Click({
        if ($WPFimage.Visibility -ne 'Visible'){$WPFimage.Visibility = 'Visible'}
        else {$WPFimage.Visibility = 'Hidden'}
    })
    

    And…that’s it!

    RevealUI Elements

    Clicking the button will reveal or hide the image. You could use the same mechanism to set an item from Disabled to Enabled as well, we’re just illustrating the principles here. Moving right along…

    Use Checkboxes

    Alright, what we’ll do now is draw a check box, and add a second Sky Bison. We’ll make both Bisons invisible, and if you check the box, you’ll get TWO bisons for the price of one! If you uncheck the box, the Bison dies. I’m sorry kid, but that’s just a fact of life.

    To save space, I’ll only show you the PowerShell behind making this work. Check the github link if you want the XAML or Project file.

    This got a bit confusing, making the second skybison only appear when the first was visible and then checking for the checkbox IsChecked when the ‘show bisons’ button is clicked.

    $WPFbutton.Add_Click({
    if ($WPFimage.Visibility -ne 'Visible'){
    if ($WPFcheckBox.IsChecked -eq $true){$WPFimage.Visibility,$WPFimage_Copy.Visibility = 'Visible','Visible'}
    else {$WPFimage.Visibility = 'Visible'}
    }
    elseif ($WPFimage.Visibility -ne 'Visible' -and {$WPFcheckBox.IsChecked -eq $false})
    {$WPFimage.Visibility = 'Visible'}
    else{$WPFimage.Visibility,$WPFimage_Copy.Visibility = 'Hidden','Hidden'}
    })
    
    $WPFcheckBox.Add_Checked({if ($WPFimage.Visibility -eq 'Visible'){$WPFimage_Copy.Visibility = 'Visible'}else{$WPFimage_Copy.Visibility = 'Hidden'}})
    

    And the result :

    Clicking the checkbox makes TWO Appa's appear!
    Clicking the checkbox makes TWO Appa’s appear!

    Use Radio Buttons and auto-populate a Combo Box/Dropdown Box

    Beginning with this example, we’ll start by combining some of the above approaches and work towards making a more useful tool, a GUI that wraps around making a new user and makes it a bit easier to set an account expiration date. We’ll use this tool to wrap the New-ADUser Cmdlet, so we should first check the requirements for that command.

    We’ll need to define the user’s Given (first name), their surName (last name), account name and PW.

    For the components we’ll use:
    • Checkbox – click the checkbox to specify the user is a temp and should expire
    • ComboBox – a drop-down box filled with acceptable places for a new user to be placed
    • Radio buttons – use the radio buttons to specify 7 days, 30 days, 90 days account life
    • Textblock – we’ll use these as pretty labels for our tool

    You can drag and drop them however you’d like, here’s what I came up with:

    Ohhh, so gooey!
    Ohhh, so gooey!

    So, here we go. We’ve got a first name, last name, logon name and password textboxes. You’ll use those as you would, expect to use them. At first, we’ll be displaying the PW in plaintext but on the next revision, we’ll add the feature to display asterisks and require you to click to reveal the PW. Finally, we have two new classes here, the combo box and the radio button.

    ComboBoxes are used to provide a user history (similar to auto-complete in your web browser of choice) or to constrict the user to only certain options. We’ll use the ComboBox to provide three or four places where a contractor can go.

    Radio Buttons are used to present the user with a number of preset options, and allow them to only select one of them at a time (usually, there is a weird thing called a tri-state radio button, which means you can select two, but those are uniformly hated and loathed by users and devs alike, and should be killed with fire and/or the delete key).

    Let’s code these up!

    I wrote a few chunks of code here. First, when the user clicks the checkbox to enable a limited timespan user, I needed to add some logic to enable the radio buttons and pick one of them, which you see here. This is done by adding a script block to the Checkbox using the Add_Checked and Add_Unchecked methods:

    #Add logic to the checkbox to enable items when checked
    $WPFcheckBox.Add_Checked({
        $WPFradioButton_7.IsEnabled=$true
       $WPFradioButton_30.IsEnabled=$true
       $WPFradioButton_90.IsEnabled=$true
        $WPFradioButton_7.IsChecked=$true
        })
    #when this box is unchecked, make sure that none of the option bubbles are selected 
    $WPFcheckBox.Add_UnChecked({
        $WPFradioButton_7.IsEnabled=$false
       $WPFradioButton_30.IsEnabled=$false
       $WPFradioButton_90.IsEnabled=$false
        $WPFradioButton_7.IsChecked,$WPFradioButton_30.IsChecked,$WPFradioButton_90.IsChecked=$false,$false,$false})
    
    

    Next, we need to link the Create User button up to the code to make a new user. Because the user may or may not be set to expire, I wanted a method to easily end up with an object called $Hash that contains the needed settings to make a new user account. I ended up writing a helper function called Get-FormField which will gather all of the settings the user specifies into the form, which is then used later on when you click the ‘Create User’ button like so:

    $WPFMakeUserbutton.Add_Click({
        #Resolve Form Settings
        $hash = Get-FormFields
        New-ADUser @hash -PassThru
        $Form.Close()})
    

    Finally,  populating the combo box, it’s actually super easy. The combo box name in this example is targetOu_ComboBox, which ends up becoming the PowerShell object $WPFtargetOU_ComboBox. We call its AddChild method to add entries to the list. I ran a quick LDAP query (thanks to this post on SuperUser for showing me the way!) to get the default OU for a new user and stored that in $defaultOU, and then manually typed in the Distinguished Name of the other OU I wanted to provide as an option.

    $defaultOU = (get-adobject -filter 'ObjectClass -eq "domain"' -Properties wellKnownObjects).wellknownobjects.Split("`n")[-1].Split(':') | select -Last 1
    
    $defaultOU,"OU=Contractors,DC=FOXDEPLOY,DC=local" | ForEach-object {$WPFtargetOU_comboBox.AddChild($_)}
    

    This results in both names pre-populating our dropdown box, like so:

    Just use $WPFCombo.AddChild() to add more items to the dropdown
    Just use $WPFCombo.AddChild() to add more items to the dropdown

    The radio buttons are used to pick 7, 30 or 90 days as the expiration date for this account. We first check to see if the Checkbox for temporary user account is checked, and if so, we then check to see which bubble/radio button is checked. We then reset $Expiration date to equal get-date.AddDays($expirationDate) and pass that along too, using Get-FormFields, this is done in lines 7-10 below.

    Here’s the full code for the ‘#Making it work’ block.

    $defaultOU = (get-adobject -filter 'ObjectClass -eq "domain"' -Properties wellKnownObjects).wellknownobjects.Split("`n")[-1].Split(':') | select -Last 1
     $WPFDefaultOUMsg.Text = $WPFDefaultOUMsg.Text -replace "@anchor",$defaultOU
    
    #gather all of the settings the user specifies, needed to splat to the New-ADUser Cmd later
    function Get-FormFields {
    $TargetOU = if ($WPFtargetOU_comboBox.Text -ne $null){$WPFtargetOU_comboBox.Text}else{$defaultOU}
    if ($WPFcheckBox.IsChecked){
        $ExpirationDate = if ($WPFradioButton_7.IsChecked -eq $true){7}`
                    elseif ($WPFradioButton_30.IsChecked -eq $true){30}`
                    elseif ($WPFradioButton_90.IsChecked -eq $true){90}
        
        $ExpirationDate = (get-date).AddDays($ExpirationDate)
        
        $HashArguments = 
            @{ Name = $WPFlogonName.Text;
               GivenName=$WPFfirstName.Text;
               SurName = $WPFlastName.Text;
               AccountPassword=($WPFpassword.text | ConvertTo-SecureString -AsPlainText -Force);
               AccountExpirationDate = $ExpirationDate;
               Path=$TargetOU;
                }
            }
        else{
        $HashArguments = 
           @{ Name = $WPFlogonName.Text;
              GivenName=$WPFfirstName.Text;
              SurName = $WPFlastName.Text;
              AccountPassword=($WPFpassword.text | ConvertTo-SecureString -AsPlainText -Force);
              Path=$TargetOU;
              }
        }
    $HashArguments
    }
    
    $defaultOU,"OU=Contractors,DC=FOXDEPLOY,DC=local" | ForEach-object {$WPFtargetOU_comboBox.AddChild($_)}
    
    #Add logic to the checkbox to enable items when checked
    $WPFcheckBox.Add_Checked({
        $WPFradioButton_7.IsEnabled=$true
       $WPFradioButton_30.IsEnabled=$true
       $WPFradioButton_90.IsEnabled=$true
        $WPFradioButton_7.IsChecked=$true
        })
    
    $WPFcheckBox.Add_UnChecked({
        $WPFradioButton_7.IsEnabled=$false
       $WPFradioButton_30.IsEnabled=$false
       $WPFradioButton_90.IsEnabled=$false
        $WPFradioButton_7.IsChecked,$WPFradioButton_30.IsChecked,$WPFradioButton_90.IsChecked=$false,$false,$false})
    
    #$WPFMakeUserbutton.Add_Click({(Get-FormFields)})
    $WPFMakeUserbutton.Add_Click({
        #Resolve Form Settings
        $hash = Get-FormFields
        New-ADUser @hash -PassThru
        $Form.Close()})
    

    For the full source, click here!

    Let’s use my favorite test-user, I like Ham.

    My wife HATES ham
    My wife HATES ham

    And in testing this in my environment…it works!

    I titled this picture 'The Expiration of Ham', it cracks me  up
    I titled this picture ‘The Expiration of Ham’, it cracks me up

    What’s next?

    I’d planned to include building a tabbed interface, making a progress bar and finally building an all-in-one management tool using PowerShell in this post, but I’ll be saving those now for part IV of the series, as my fingers are tired and me no-wanto-writey anymore! Until then, please let me know here, on e-mail, or on Twitter if there are any other GUI features you’d like to see me outline!



    Part IV – DSC – One-Click Domain Controller

    $
    0
    0

    IntroToDsc

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


    Hey guys, I haven’t forgotten about you, but it’s been a very busy month here, with me traveling to Redmond ( Microsoft HQ! I have to post a pic!) for some exciting Microsoft meetings and then back and forth to Minneapolis for a new client!

    2015-06-02 13.48.56

    I didn’t know what to do with my hands in this picture.


    I’ve been receiving your messages and have now released the final step in this one-click Domain controller DSC Build. To recap, we left off with a DSC Config that would make our machine a domain controller, but that was it.

    We didn’t have functional DHCP for clients to find us, or any other bells and whistles. In this post, we’ll be adding on DHCP so clients can get an IP address, as well as DNS to help our Machines Domain Join to our new DC. If you tried Domain Joining with our build from last time, you would have been quite disappointed. Not this time!

    What has to go :

    Previously, we had a single config file that did some really good stuff for us, setting up a workgroup and then making a new local admin. We’re going to gut all of that stuff, because setting up a system with a DSC Config needs to be idempotent. That means that the config needs to be able to run over and over without breaking stuff, and the previous build would do just that. If we draft a config that will result in changes every time the config is validated, we’ve created a problem.

    The issue in our last config stemmed from the fact that it would change the system’s name and also add it to a work group. Then, later in the config we make it a domain controller. What would happen if the DSC config were re-evaluated is that the machine would throw an error when it got to the portion of the .mof telling it to join a workgroup. Domain Controllers can’t leave a domain until they’ve lost all of their FSMO Roles, so this is an invalid config. Bad Stephen!

    As it is, that bit about joining a workgroup turned out to be wasted space, so we’ll remove that too. The full code is available below, as well as here on my GitHub site.

    What’s new and what does it do

    For this post, you’ll need the xNetworking Resource, found here

    As well as the xDHCPServer Resource, found here

    You’ll also need the xComputerManagement and xActiveDirectory Resources, which can be found here:

    Prepare your DSC Target Node by copying the folders in the zips to the Modules folder, in this path

    $env:ProgramFiles\WindowsPowerShell\Modules

    The big new steps we’re adding are the following:

    Set a Static IP Address

    The end goal of this whole shebang was to have a working DHCP server, and DHCP can’t give out IP Addresses unless they have a fixed IP themselves. In this config block , we set a Static IP of 10.20.30.1 and rename the Adapter along the way.

    xIPAddress NewIPAddress
            {
                IPAddress      = "10.20.30.1"
                InterfaceAlias = "Ethernet"
                SubnetMask     = 24
                AddressFamily  = "IPV4"
    
            }
    
    

    Enable the DHCP Windows Feature

    This one was kind of a pain, as it was hard to figure out what the name was of the DHCP Server Role! Turns out it’s DHCP not DHCPServer (as it is listed in Get-WindowsFeature). All we do here is make sure that the DHCP server is installed, and we do it after configuring the IP address to prevent an error which would shut down our config.

    WindowsFeature DHCP {
                DependsOn = '[xIPAddress]NewIpAddress'
                Name = 'DHCP'
                Ensure = 'PRESENT'
                IncludeAllSubFeature = $true                                                                                                                               
    
            }  
    
    

    Enable the DHCP Address Scope

    You can get super complex with Windows Server Networking using DHCP and DNS, but I always like to keep things simple, especially in my testlab. This configuration resource essentially runs us through the DHCP Wizard and ensures that a DHCP Scope Exists, giving out IP addresses from 10.20.30.5 all the way up to 250.

       xDhcpServerScope Scope
            {
             DependsOn = '[WindowsFeature]DHCP'
             Ensure = 'Present'
             IPEndRange = '10.20.30.250'
             IPStartRange = '10.20.30.5'
             Name = 'PowerShellScope'
             SubnetMask = '255.255.255.0'
             LeaseDuration = '00:08:00'
             State = 'Active'
             AddressFamily = 'IPv4'
            } 
    
    

    Specify the DNS server for DHCP clients to use

    If you don’t get this part, your DNS Clients will throw up an ‘
    ERROR: Could not join to the domain VAS_ERR_DNS: unable to lookup any DNS SRV records for’, which should be your clue to ensure that you’ve specified option 6 in your DNS settings

    xDhcpServerOption Option
         {
             Ensure = 'Present'
             ScopeID = '10.20.30.0'
             DnsDomain = 'fox.test'
             DnsServerIPAddress = '10.20.30.1'
             AddressFamily = 'IPv4'
         }
    
    

    The Complete Config

    If you’re following from home, go ahead and delete everything from the last DSC post from line 29 to 54.

    Or, if you’re laz–uh, more efficiently minded, copy and paste this code instead.

    $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,  $firstDomainAdmin,
            [Parameter(Mandatory)][string]$DomainName,
            [Parameter()][string]$UserName,
            [Parameter()]$SafeModePW,
            [Parameter()]$Password
        ) 
    
        #Import the required DSC Resources
        Import-DscResource -Module xActiveDirectory
        Import-DscResource -Module xNetworking
        Import-DscResource -module xDHCpServer
        Import-DscResource -Module xComputerManagement 
    
        Node $NodeName
        { #ConfigurationBlock 
    
            xComputer NewNameAndWorkgroup
                {
                    Name          = $MachineName
    
                }
    
            WindowsFeature ADDSInstall
            { 
    
                Ensure = 'Present'
                Name = 'AD-Domain-Services'
                IncludeAllSubFeature = $true
            }
    
            WindowsFeature RSATTools
            {
                DependsOn= '[WindowsFeature]ADDSInstall'
                Ensure = 'Present'
                Name = 'RSAT-AD-Tools'
                IncludeAllSubFeature = $true
            }
    
            xIPAddress NewIPAddress
            {
                IPAddress      = "10.20.30.1"
                InterfaceAlias = "Ethernet"
                SubnetMask     = 24
                AddressFamily  = "IPV4"
    
            }
    
            WindowsFeature DHCP {
                DependsOn = '[xIPAddress]NewIpAddress'
                Name = 'DHCP'
                Ensure = 'PRESENT'
                IncludeAllSubFeature = $true                                                                                                                               
    
            }  
    
            WindowsFeature DHCPTools
            {
                DependsOn= '[WindowsFeature]DHCP'
                Ensure = 'Present'
                Name = 'RSAT-DHCP'
                IncludeAllSubFeature = $true
            }  
    
            xADDomain SetupDomain {
                DomainAdministratorCredential= $firstDomainAdmin
                DomainName= $DomainName
                SafemodeAdministratorPassword= $SafeModePW
                DependsOn='[WindowsFeature]RSATTools'
                DomainNetbiosName = $DomainName.Split('.')[0]
            }
    
            xDhcpServerScope Scope
            {
             DependsOn = '[WindowsFeature]DHCP'
             Ensure = 'Present'
             IPEndRange = '10.20.30.250'
             IPStartRange = '10.20.30.5'
             Name = 'PowerShellScope'
             SubnetMask = '255.255.255.0'
             LeaseDuration = '00:08:00'
             State = 'Active'
             AddressFamily = 'IPv4'
            } 
    
            xDhcpServerOption Option
         {
             Ensure = 'Present'
             ScopeID = '10.20.30.0'
             DnsDomain = 'fox.test'
             DnsServerIPAddress = '10.20.30.1'
             AddressFamily = 'IPv4'
         }
    
        #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
    

    Now that we know what we’re doing, let’s give it a whirl!

    Testing it out

    My network is laid out like this

    Layout

    I’ve got my future DHCP Server and a random client Windows 10 VM both sitting on the same network. Now we’re going to enforce the configuration on my DSC Client, and then watch and see my workstation pull down a DHCP Address!

    You might be thinking:

    ‘Hey Stephen, why not just copy the files down with DSC!’

    and that’s a great question. As it turns out, you can do something like that by using what’s called a DSC Partial Configuration…which I’m still figuring out. Once I understand it, I’ll write a post about it. The long and short of it now is that you can’t reference a resource and also copy a resource within the same config because…uh…reasons.

    You’re boring me, let’s get to it!

    The only thing that’s left is to hit F5 and watch as my machine gets config’d! First we apply the new computer name… which needs a reboot

    SetComputerName

    Now, we reboot and just relaunch the configuration. We could just wait…but it’s more fun to hit -Force and watch the whole thing happen in real time

    MakingaDC

    If all of this worked (and it looks like it did!) we should now be able to go over to our test machine and run a DHCP /renew and see an IP address come over.

    DHCP worked!

    An important piece of getting this Domain Controller accepting domain joins is to make sure that new PCs to the domain can find the DC. This means that they need to ask a DNS server for the SRV record of a Domain Controller holding the Global Catalog role. We’ll run a ipconfig /all and see if our DNS Server setting is registered.

    DNS server came alone

    Now is where I started to get excited. See, my whole reason for going down this path is that I think making a domain controller in a Lab environment can be very daunting for first-timers, and wanted to help lower that barrier to entry. If my client machine can see the DC, then it should be able to Domain Join now. So the moment of truth…

    MomentOfTruth

    Now…to hit OK…

    Welcome to the FoxDeploy Domain!!!
    Welcome to the FoxDeploy Domain!!!

    Wrapping it up

    I will admit, I jumped up and down when I got this part to work. We now have a one-click, single-stop Domain Controller build, perfect to use a stepping stone into whatever else you’d want to do on a domain controller. It really is kind of amazing to me that this all works, knowing how hard it would be to do this level on configuration using something like SCCM/Configuration Manager.

    From here? The next step in our series will have us joining clients to this domain as well, and from there, we can do whatever you’d want to do with DSC. If you’ve got an idea or a situation from work, send me a message, and you might just be the next blog post here in this series.


    Check your US Postal Service Package Status with PowerShell!

    $
    0
    0

    Hey guys!

    Been a while since I wrote up a fun little PowerShell REST API tool, so here we go! I taught a four day PowerShell boot camp for a Fortune 50 company, and during the event, one of my students called out the need to track his package delivery online.

    The discussion quickly turned high-dork, as we dug in to see if there was a convenient way to get this information.

    It turns out that the United States Postal Service has a REST API we can use to check for the status of package delivery.

    You’ll need to sign up here for REST Api access first : https://www.usps.com/business/web-tools-apis/welcome.htm

    To make a request, you need to query the following URL:

    http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=$xml
    

    You’ll notice the $XML, this is a special XML payload that needs to contain data in a certain format, here’s the template for a package delivery status page:

    <?xml version="1.0" encoding="UTF-8" ?>
    <TrackFieldRequest USERID="$userID">
        <TrackID ID="$pkgID">
       </TrackID>
    </TrackFieldRequest>
    

    $pkgID will be a long package tracking number you’ll get in your e-mail when something is mailed to you, it’s about 22 characters long.

    When you’ve signed up, you’ll get an e-mail like this one, this is your userId

    SignUpInfo

    From here on, it is pretty straightforward, all we have to do is use Invoke-WebRequest to query the URL and then check for some errors (I’ve planned for you to forget to provide your UserID, or to not provide a PKgID. If you leave these out…I’m not sure what you expect this to do :) ). If no errors are there, your package information will be contained within $Response.Content.TrackResponse.TrackInfo.TrackDetail.

    Here’s an image of what it looks like!

    ItWorks

    Here’s the code!

    <#
    .Synopsis
       Use this tool to query the US Postal Service to find out where your packages are!
    .DESCRIPTION
       Use this tool to query the US Postal Service to find out where your packages are!
    .EXAMPLE
        Get-USPSPackageStatus -UserID 578FOXDEPLOY -pkgID 94HAMHAMHAMHAM95621412407
    
    EventTime EventDate     Event                                 EventCity EventState EventZIPCode EventCountry FirmName Name AuthorizedAgent
    --------- ---------     -----                                 --------- ---------- ------------ ------------ -------- ---- ---------------
    8:45 am   June 15, 2015 Out for Delivery                      MARIETTA  GA         30066                                   false          
    8:35 am   June 15, 2015 Sorting Complete                      MARIETTA  GA         30066                                   false          
    6:00 am   June 15, 2015 Arrived at Post Office                MARIETTA  GA         30066                                   false          
    1:28 am   June 15, 2015 Departed USPS Facility                ATLANTA   GA         30320                                   false          
    2:43 pm   June 14, 2015 Arrived at USPS Facility              ATLANTA   GA         30320                                   false          
    4:44 am   June 13, 2015 Departed USPS Facility                ANAHEIM   CA         92899                                   false          
    9:31 pm   June 12, 2015 Arrived at USPS Origin Facility       ANAHEIM   CA         92899                                   false          
    8:16 pm   June 12, 2015 Accepted at USPS Origin Sort Facility SANTA ANA CA         92704                                   false          
    
    
    .EXAMPLE
       Get-USPSPackageStatus -UserID 578FOXDE2122 -pkgID 94HAMHAMHAMHAM9562141240
    The most recent event we can track is below
    
    EventDate                                                                     EventTime                                                                     Event                                                                        
    ---------                                                                     ---------                                                                     -----                                                                        
    June 15, 2015                                                                 1:43 pm                                                                       Delivered, In/At Mailbox                                                     
    Here is the summary of what we know about the package
    
    
    
    EventTime               EventDate               Event                   EventCity               EventState             EventZIPCode           EventCountry           FirmName               Name                   AuthorizedAgent       
    ---------               ---------               -----                   ---------               ----------             ------------           ------------           --------               ----                   ---------------       
    8:45 am                 June 15, 2015           Out for Delivery        MARIETTA                GA                     30066                                                                                       false                 
    8:35 am                 June 15, 2015           Sorting Complete        MARIETTA                GA                     30066                                                                                       false                 
    6:00 am                 June 15, 2015           Arrived at Post Office  MARIETTA                GA                     30066                                                                                       false                 
    1:28 am                 June 15, 2015           Departed USPS Facility  ATLANTA                 GA                     30320                                                                                       false                 
    2:43 pm                 June 14, 2015           Arrived at USPS Faci... ATLANTA                 GA                     30320                                                                                       false                 
    4:44 am                 June 13, 2015           Departed USPS Facility  ANAHEIM                 CA                     92899                                                                                       false                 
    9:31 pm                 June 12, 2015           Arrived at USPS Orig... ANAHEIM                 CA                     92899                                                                                       false                 
    8:16 pm                 June 12, 2015           Accepted at USPS Ori... SANTA ANA               CA                     92704                                                                                       false                 
    
    
    Your package was delivered!
    #>
    function Get-USPSPackageStatus {
    param($UserID="Something",
    $pkgID='9400111899562141240724',[switch]$Passthru)
    
    $xml = @"
    <?xml version="1.0" encoding="UTF-8" ?>
    <TrackFieldRequest USERID="$userID">
        <TrackID ID="$pkgID">
       </TrackID>
    </TrackFieldRequest>
    "@
    
    $url = @"
    http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=$xml
    "@
    
    #Get the response and save in a variable $Response
    $response = Invoke-WebRequest $url
        
        #Check for an error
    
            if ((([xml]$response.Content | select -ExpandProperty Error -ErrorAction SilentlyContinue) -ne $null) -or ([xml]$response.Content | select -ExpandProperty TrackResponse | select -Expand TrackInfo  | select -ExpandProperty Error -ErrorAction SilentlyContinue) -ne $null){
        
                if (([xml]$response.Content | select -expand Error -ErrorAction SilentlyContinue).Number -eq "80040B1A")  {
                    Write-Warning "We hit an error: Check your user credentials, specifically the value for `$userID :$UserID"
                    BREAK
                    }
    
                if (([xml]$response.Content | select -ExpandProperty TrackResponse | select -Expand TrackInfo  | select -ExpandProperty Error -ErrorAction SilentlyContinue) -ne $null){'ham'
                    Write-Warning "We hit an error: Check the package ID you specified, `$pkgID :$pkgID"
                    BREAK
                }
                    
            }
    
        #Show the most recent event
    
        $status = [xml]$Response.Content  | select -expand TrackResponse | select -ExpandProperty TrackInfo | select -expand TrackSummary | select EventDate,EventTime,Event
        
        Write-output "The most recent event we can track is below"
        
            if ($Passthru){$status}
                else{
                    $status | select EventDate,EventTime,Event | Format-Table -AutoSize
                }
    
        Write-output "Here is the summary of what we know about the package"
        #take the response which is XML and Cast it to make PowerShell parse it better
        $details = [xml]$Response.Content  | select -expand TrackResponse | select -ExpandProperty TrackInfo | select -expand TrackDetail 
    
            if ($Passthru){$details}
                else{
                    $details | Format-Table -AutoSize
                }
    
        if ($status.Event -like "Delivered*"){
            Write-Host -ForegroundColor Green "Your package was delivered!"
        }
    
    } 
    
    

    Upcoming Events: PowerShell Training, by me!

    $
    0
    0

    If you’ve been following my blog, chances are that you enjoy PowerShell, and hopefully like my take on presenting information and the like! I’m happy to announce that we’ve got open registration now for two exciting upcoming PowerShell training events!

    The South-Eastern PowerShell Bootcamp Tour

    download

    This is THE premier hands-on PowerShell Bootcamp event.   Come with no command line, DOS, VBScripting or other experience and leave writing powerful one-liners and scripts.  You’ll receive poignant tips from the field in a custom course designed and taught by Microsoft MVP of Scripting, Stephen Owen.  Bring your laptop and appetite, as Breakfast and Lunch are included!

    Agenda

    • Intro to PowerShell – learn about what it is, who made it, why it’s different, and why some are saying ‘Learn PowerShell or get used to saying ‘…would you like fries with that?’
    • Diving into the Shell – starting with command line basics, we’ll build up through demo after demo and hands-on lab work to see just a few of the unique and productivity enhancing tools that PowerShell provides.
    • Enter the Pipeline – picking up speed, we’ll cover pipelining fundamentals, and by the end of this module, you’ll be able to perform very complex operations with only a handful of characters.
    • We’ll Do it Remote – we’ll explore the incredibly powerful built-in remote capabilities PowerShell offers, to let you scale from one-on-one to one-to-many and one-to-environment with your commands.  We’ll cover all the safety basics too!
    • Data Processing – able to natively process numerous datatypes, we’ll explore in-depth the data capabilities PowerShell provides, and end with a powerful section on report building sure to impress your colleagues and peers!
    • Advanced Scripting Techniques – We’ve done one-liners, now it’s time to scale and we’ll cover all of the tools you’ll need to quickly get started with enterprise ready scripting, including best practices for roll-back, code review and logging.
    • Spelunking with WMI – inevitably you’ll run into a situation for which a pre-made PowerShell cmdlet doesn’t exist yet, and at that point, it’s time to make our own!  We’ll go on a guided tour through the WMI Management Repository, and learn the few commands needed to extract anything from Windows with one or two lines of code.
    • Functions and Toolmaking – we’ll finish strong by expanding our scripts into reusable code and functions you can distribute to your peers.  By popular demand, the toolmaking module is being expanded into multiple units, now also covering the fundamentals of Easy Graphical User Interface tool creation using Visual Studio.  You don’t need to be a developer, as we’ll cover everything you need to know to start making GUIs in minutes.  You should leave this module with branded tools you can take back and put into use at the office!

    Course Prerequisites

    Time: Come ready to learn, as this is an all-day course.  Attendees will have access to the internet, but it is recommended that they have the full three days allocated to learn.  Because this is a boot camp style course, we will be moving swiftly as a group.

    Laptop : Bring a capable laptop, PowerShell v4 capable.  You’ll need a minimum of Windows 7 SP1 and DotNet FrameWork 4.5.  We’ll try to provide power strips to accommodate all attendees, but it is recommended to have a system with long battery life, if possible.

    Experience: So long as you have a basic understanding of Windows concepts like the Control Panel and Registry, we’ll build from the ground level up.  We’ll talk about a number of common services like Exchange and Active Directory, and the basics of their management.

    This is intended as a Level 100-150 course, meaning you do not need to be an expert to attend.  


     Registration

    The first four stops of our Bootcamp tour are now completed, but registration is now open for our Summer stops, in Atlanta and Birmingham!


    PowerShell Hyper-V Configuration Backup report

    $
    0
    0

    In preparing my home lab to upgrade to Windows 10 (I’ll be leaving server 2012 R2 w/ Desktop experience behind, after years as my daily driver).

    I realized that I’d essentially need to rebuild my Hyper-V setup (which really isn’t so bad, just reimporting my VM .xml files and then setting the VHDs the right way again) when I got to Windows 10, so I wanted a quick reference of which VM files were where and how my switches were laid out.

    This is what I came up with!

    VMConfig
    It gives us first the configuration of all of our switches, whether they’re bound to a physical adapter and whether the management OS shares the NIC, if so. It then breaks down by VM, where the .xml file lives for it, which drives it has mounted, which NICs, whether Dynamic RAM is turned on and finally the startup memory.

    Pretty much everything you’ll need to know when rebuilding VMs and you still have access to the source files!

    This depends on the .css source file found here in my DropBox, to give us pretty colors :)

    $Switches = Get-VMSwitch | Select Name,SwitchType,AllowManagementOS,NetAdapterInterfaceDescription
    
    
    $VMs = get-vm | % {
           $VMXML = gci $_.Path -Recurse -Include *xml | ? baseName -like "$($_.VMId)*"
           $VMDisk = Get-VMHardDiskDrive -VMName $_.Name | Select -expand Path 
           $VMNics = Get-VMNetworkAdapter -VMName $_.Name | select -expand SwitchName
           [pscustomobject]@{VMName=$_.Name;
                VMPath=$VMXML;
                Drives = $VMDisk -join "`n"
                NICs = $VMNics -join ";"
                DynamicMemory = $_.DynamicMemoryEnabled;
                StartupMemory = $_.MemoryStartup/1mb
                }
           
           } | ConvertTo-Html -Fragment
    
    $base = $switches
    
    
    $companyLogo = '<div align=left><img src="C:\Users\Stephen\Dropbox\Speaking\Demos\logo.png"></div>'
    $header = @"
     
     $companyLogo
     <h1>Hyper-V Configuration Report</h1>
     <p>The following report was generated at $(Get-Date) and contains the needed information to recreate the Hyper-V environment which existed before on $($Env:Computername)
    </p>
    
    
     <hr>
     <h3>Switch Configuration</h3>
    "@
    
    $post = @"
    <h3>VM Configuration</h3>
    $VMs
    
     <h3>These VMs were found on the L: and V: drives</h3>
    "@
    
    $HTMLbase = $base | ConvertTo-Html -Head $header -CssUri "C:\Users\Stephen\Dropbox\Speaking\Demos\style.css" `
                                -Title ("VM Configuration Report for $((Get-Date -UFormat "%Y-%m-%d"))") `
                                -PostContent $post
                                                
                                                
    $HTMLbase | out-file l:\VMBackup_$((Get-Date -UFormat "%Y-%m-%d"))_Log.html 
    
    
    

    Using PowerShell to find drivers for Device Manager

    $
    0
    0

    One of the most arduous tasks for a ConfigMgr admin is to build images to support new models of hardware for Operating System Distribution. As a SCCM Engineer, I’ve done this process for probably more than a hundred models now, and here’s how it normally goes.

    How I solve missing drivers

    • Connect my new device to the network
    • Do a fresh install off of a thumb drive or PXE of my current image and see what breaks
    • Boot into Windows and then look at Device Manager.

    At this point, I’ll either jump up and down or I start crying, based on how many of my devices have an Exclamation point.

    unhappy_device_manager

    The process to figure out the device is to look at the device, in device manager->Properties->Details->Hardware IDs

    How to find devices

    I then lookup the info in an online repository. Like the PCI Database or Windows Update to find the actual driver from the vendor.

    How to search

    This works…but it could be better.

    Using PowerShell

    Johan Arwidmark is a well-respected SCCM God. He’s pretty much the best, period. He had a great blog post about the method he uses to find drivers, centered around a Walkthrough on a newer Lenovo system.

    If you look into that post you’ll find a PowerShell one-liner that gives you a listing like this:

    Johan

    Which you can then use to step through your hardware reference site of choice.

    Taking it one step further

    I’ve been wanting to write something like this for a while, and a conversation with my friend Julie Andreacola over at CBfive prompted me to start coding. Why look up item by item, when you can use PowerShell to search through all of the listings and resolve all the names at once? Full download link on the text below.

    Get-UnknownDevices

    Using Johan’s code as a starting point, I’ve expanded it out, to now locate missing devices, grab the hardware and vendor IDs and then search the database for the device name using PowerShell!

    Standard output looks like this

    Get-UnknownDevices Standalone

    When used in conjunction with an offline machin, simply Export the file

    Export

    Then Import on another system with Internet Access using the -Import switch to resolve those drivers issues

    Import

    I hope you like it! Full code will live on GitHub here :
    https://github.com/1RedOne/Get-UnknownDevices


    Recovering your Dedeuped Files on Windows 10

    $
    0
    0

    Were you one of those who installed the server 2012 binaries into your Windows 8.1 to enable Disk Deduplication? Did you turn on dedupe on all of your drives, saving hundreds of gigs of storage space, then upgrade to Windows 10?

    Upon boot, were you greeted with frequent ‘the machine cannot access the file’ errors? If so, then this is the guide for you!

    What happened to my stuff? Did I lose it all?

    NO!  You did not lose your files.  What happened when you ran deduplication on your files, is that Windows gradually scrubbed all of the common elements out of many files in order to compress them, much like what happens when you put files into a .zip or .rar archive.

    All of your files still live on that disk or volume, happily compressed in their Optimized state and sitting snuggled up to their brethren.  However, until you ‘Unoptimize’ them, or connect the disk to a system with Windows Server Deduplication enabled, you cannot access any items bigger than 32KB (the minimum size for block level dedupe).

    You have two methods to recover your files

    These are both very similar, in the outcome, only the beginning differs.

    Method 1 – Move the disk to another computer

    Physically take the disks that are hosed and attach them to another system with ‘Data Deduplication’ enabled, could be any OS which supports it, even 8.1.  Once you’ve done this, proceed.

    Method 2 – use Hyper-V to virtually move the disk

    This is the method I used.

      1. Stand up another VM (in my case Server 2016 Tech Preview, though you could use 2012, 2012 R2, or even Windows 8.1) and installed the Storage -> Data Deduplication role on it.  Make sure to add one or more SCSI Hard Drive Controllers, one for each drive you need to ‘redupe’.Dedupe/redupe is incredibly IO and memory intensive.  Bump up the VMs stats to super level if you want this to be done quickly.
      2. Take the disks you need to ‘redupe’ offline.  I then went to my affected volumes in Disk Manager on my local machine and took the disks offline.  This is necessary in order to make this physical drive a ‘Passthru’ disk, which essentially connects the spinning disk to your VM.

        Once the disk is offline, we can attach it to a VM.  Do NOT accidentally format your drive here.
        Once the disk is offline, we can attach it to a VM. Do NOT accidentally format your drive here.

      3. In Hyper-V, attach the disk to your VM, by attaching it as a physical disk.redupe2
      4. Go into your VM and run Disk Management, and bring the disk online.  Do NOT accidentally format your disk here either.

        If you format your drive here, you'll now have even more problems.
        If you format your drive here, you’ll now have even more problems.

      5. Once the disks are all online in your VM, launch PowerShell as an admin and then run Get-DedupStatus to verify that all of your drives are listed.

        redupe4
        Say goodbye to all of this optimized space…

      6. For each drive, run the following Cmdlet.
        Start-DedupJob -Volume "D:" -Type Unoptimization

        If you only have one drive, run the cmdlet with -Wait so you can see how incredibly long this is going to take.

      7. Wait forever.  It takes a REALLY, REALLY REALLY really super long time to ‘redupe’ your files.
        It's been stuck at 0% for ten minutes!
        It’s been stuck at 0% for ten minutes!

        If you want to know how long it will take if you didn’t run this in -Wait mode, you can run

        Get-DedupJob

        to see how long it will take, but don’t sit around.  It will take forever.

      8. Send Microsoft an e-mail or vote for this issue on User Voice.  I’ve been to Redmond, and I can confidently say that they really do pay attention to user feedback on issues like this.  Seriously, Windows Desktop Enterprise and Pro could use some more distinguishing features.  Vote this up and hopefully we’ll get Dedupe as a standard feature with Threshold in October.

    Big thanks to fellow Microsoft MVP Mike F Robbins for his tweets and post on the matter, and to Microsoft Storage Program Manager Ran Kalanch for helping me to understand exactly what I’d done to my drives at midnight of the Windows 10 release.
     


    New in Windows 10: Changing home to Pro w/ no reinstall!

    $
    0
    0

    One of the hotly anticipated features of Windows 10 is the ability to change your Windows version on the fly, without even needing to reinstall the OS!

    This means that if you’re a Home user and want to start using advanced features like Hyper-V, which is only available on Pro, or to begin using your computer with BranchCache and other technologies only found on Enterprise, you don’t need to reinstall anymore.

    WalkThrough

    You decide one day that Hyper-V sounds awesome and you want to be able to run Virtual Machines on your laptop/desktop. You go to ‘Turn Windows Features on or off’ and

    …you don’t see Hyper-V listed

    NoHyperV
    Hyper-V is a Pro and Enterprise only feature, sorry!

    As we know now, Hyper-V is a pro and enterprise feature only.  Double checking the version (using Windows+X, System) confirms that we’re running Windows 10 Home.

    NoHyperV1

    However, we now have the option to buy an upgrade and apply it at any time.  Assume you’ve paid to upgrade and now have a valid Windows 10 Pro license.  You can go back to System, and on the bottom, click Change product key.

    NoHyperV2

    This will prompt for Admin rights, so provide them, and you’ll see this screen.

    NoHyperV3
    No, this is not a valid license key.

    If the key is valid, you’ll be prompted that your system needs to reboot.NoHyperV4

    NoHyperV5
    I hope you saved your files, as the upgrade waits for no man.

    After a reboot, you’ll see the following.
    NoHyperV6

    And just to confirm…

    NoHyperV7
    Pretty cool that we can now do this at any time without having to reinstall windows. The same works to go back to Home as well, just provide your license key and away you go!


    ‘GUI your life with PowerShell’– Slides and Scripts available

    Part IV – PowerShell GUIs – how to handle events and create a Tabbed Interface

    $
    0
    0

    series_PowerShellGUI

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


    Where we left off

    Previously in Part III of our GUI series, we left off with a review of some advanced GUI options, things like radio buttons, text replacement and things like that. In this section, we’ll grow our tool from Part I, our lowly Ping tool, and expand it to become a fully-fledged remote PC management tool. We’ll do this to learn our way through some new GUI concepts we haven’t run into before. I’ll take you through making most of this tool, and leave you at a place where you should be able to take it the rest of the way.

    What we’ll cover

    If you walk through this guide, I guarantee that we’ll cover each of these items below. If there is something you’d like to see covered in a future post, send me an e-mail! Each of the items covered in this post came from requests from my readers.

    Seriously.

    I am gathering input for post five right now, so let me know what’d you’d like to do with PowerShell, and we can make it happen.

    Making a Tabbed Interface

    We’re going to talk about the right way to use this element, to make it obvious to users (something called ‘Discoverability’) that more tools and options exist, if they’d only click this tab and explore around a bit! In tab one, we’ll drop in the whole of our pinging tool from Part I of this walkthrough, while we’ll hide extra functionality in tabs two, three and four. For extra niftiness, we’ll have the others tabs be disabled (unclickable) until the user first verifies that a PC is online in tab one.

    Handling UI Events

    This is the part that has me the most excited, mostly because I just learned about how to do it, and it solves so many issues. Want to have your GUI run a chunk of code when the user edits a textBox. Or handle the event which is triggered if the user moves the mouse over a button? We can even wait till the user is finished entering text, and then run a short chunk of code to validate an entry. These are all real world scenarios for a User Interface Developer, and we can easily deal with them in PowerShell.

    Let’s get started.

    Tabs, not just for playing ‘The house of the rising sun’

    Now, I said we’ll be bringing back our pinger…and we kind of will. But we’re not ready to open up that code, just yet.

    See, the thing is that while it IS possible to add tabs to an existing project or UI, it’s much easier to start off with a new project, get the tabs, status bar, dock panels, etc setup the way you want them to look, and then copy and paste in other GUI elements from Visual Studio, or some XAML code.

    Let’s start the right way with a tabbed UI. We’ll be making a new project for this walkthrough, and at this point, it should feel familiar. Launch Visual Studio, click New, New Project, WPF.

    Pick a good starting size for your form (I’ll do 345×450), then add a Tab control from the toolbox.

    I like to draw a somewhat wide tab, like this.

    00_Fill all

    Then, right-click the top of the tab…uh, holder, right next to your last tab and choose Layout-Fill All. I do this because it’s actually surprisingly hard to drag your tab to fill the window, so this is the easy shortcut.

    01_Fill all
    This will expand the tabs to fill the whole UI.

    Now that we have our tabs taking up the whole window, we can add more by right-clicking the background and choosing ‘Add Tab Item’. We can also rename the tabs by clicking one and using their properties, or going down to the XAML and changing the name property there.

    For our purposes, we’ll name the Tabs ‘Computer Name’, ‘System Info’, ‘Services’ and ‘Processes’. Now, for the process of adding content to tabs.

    This is a bit tricky

    When it comes to adding guts to our tabs, I don’t like to build the new guts for each tab within the tab window itself.

    No, instead, I like to make a new window (right click the project in Solution Explorer, Choose Add, then New Window, and pick Window WPF) for each of my tabs. Then I can decorate them there, get them sized up, and then copy-paste the XAML or the UI elements into the Tab where they’ll reside when I’m done.

    This way, I make a good ComputerConnection.XAML window once, then I can copy it and paste it as many times as needed, and reuse it later on down the line. If the code’s already embedded into a tabcontrol, pulling it out to reuse later can be tricky.

    Making a new window for each tab is much easier for development

    Now with a new window created, I pick the tools I need, build it here, and then when it’s finished, I copy and paste it into the tabbed UI. I found it quite difficult to do this by manually dragging and dropping tools from the toolbox into the tab itself. If you’ve got a gamer mouse or much better dexterity, or are just plain cooler than me (you probably are, I write a blog about a scripting language, that doesn’t scream ‘Taking the cheerleader to prom’ to me) then go ahead and build your whole tool, bypassing the method I provide here.

    This is what a blank window looks like. Time to put some clothes on this naked baby!
    This is what a blank window looks like. Time to put some clothes on this naked baby!

    So I’ve got my new window which I’ve named and titled ComputerName_Tab. I’m going to drop in an image, a textblock, a button, and a textbox, and change the background color to something dark and moody looking.

    Important tip

    This is a time to make sure we’re using good naming conventions for our UI elements. When we paste our XAML over to PowerShell snippet, we’ll have a lot of $WPFButtons to keep track of, if we’re not careful. From now on, when we add elements, lets take the time to give them a logical name, like ‘VerifyOnline_Button’ and ‘ComputerName_textbox’, etc.

    04_e

    Here’s my XAML if you want to be lazy. Just copy and paste this after the window declaration in Visual Studio.

    <Grid Background="#FF0B4A80">
        <TextBlock TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="22,21,101,0" >
        This is the FoxDeploy official Awesome Tool.  To begin using this tool, first verify that a system if online, and then progress to any of the above tabs.
    </TextBlock>
        <TextBox x:Name="ComputerName" TextWrapping="Wrap" HorizontalAlignment="Left" Height="32" Margin="21,142,0,0" Text="TextBox" VerticalAlignment="Top" Width="135" FontSize="14.667"/>
        <Button x:Name="Verify_Button" Content="Verify Online" HorizontalAlignment="Left" Margin="174,142,0,0" VerticalAlignment="Top" Width="93" Height="32"/>
        <Image x:Name="image1" Stretch="UniformToFill" HorizontalAlignment="Left" Height="98" Margin="10,174,0,0" VerticalAlignment="Top" Width="245" Source="C:\Users\Stephen\Dropbox\Speaking\Demos\module 13\Foxdeploy_DEPLOY_large.png"/>
        </Grid>

    Now, that I’ve got this configured the way that I like it, I’m going to copy this whole body of code, and paste it into the master .xaml file, the one with the tabs we made earlier.

    When you’re selecting XAMl, you only need to copy and paste everything from

    <Grid> to </Grid>
    Overwrite the you see here
    In case it’s not clear, you want to paste your code in between the TabItem tags

    When you click away after pasting, the window will update in Visual Studio and you should see your same UI compressed into the tab. You may need to resize some things to make them fit, but this is looking good so far!

    05
    It’s the same UI, now within the tabbed screen!

    Now, let’s flesh out the rest of the tabs. For System Info, I’m going to copy and paste the formatting we already did for Part II of this blog series, excerpted here, after renaming some elements and removing cruft. Don’t worry about looking up the post unless you want to, what I’ve got here will work just fine if you’re following along.

    <Grid Background="#FF0B4A80">
                        <Button x:Name="Load_diskinfo_button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
                        <ListView x:Name="disk_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="325" RenderTransformOrigin="0.498,0.169">
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="80"/>
                                    <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="80"/>
                                    <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="80"/>
                                    <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="80"/>
                                </GridView>
                            </ListView.View>
                        </ListView>
                    </Grid>
    

    Alright, pasting that in to our UI, we should see the following.

    06

    This is a great time to take this code and try and run it in PowerShell. If it works…you’re in business, and the next step is to hook up some of the UI elements with some Add_ methods.

    Copied and paste and it worked in PowerShell!
    Copied and paste and it worked in PowerShell!

    At this point, we’ll move on to the coolest part, which is how to handle events.

    Events and how to handle them

    This was something that confused me for a LONG time., in fact it’s been the dreaded question of my bootcamps as well. This is how I used to handle questions like these.

    Student: ‘Stephen, how do I do something when the textbox loses focus, or when the text changes, or when blahBlah happens?’.
    Me:
    Me:
    Me: * runs to bathroom and hides till day is over*

    Here’s how it works.

    Every GUI element in a form has a collection of Events that get triggered on certain conditions. They have names which are pretty easy to understand too, things like MouseEnter, MouseLeave(this event name has always sounded kind of disappointed to me), texthasChanged, etc.

    When we’re loading this our form code into memory, the c# compiler is transforming our XAML into intermediate language code, another phrase for code on it’s way to becoming machine executable code. When that happens, we get a whole lot of what developers call ‘syntax sugar’–which sounds awfully diabetic–but means that the tools help us do work and give us shortcuts.

    One of those shortcuts is that the compiler snorts through our GUI like a pig looking for truffles, finds all of the events associated with the objects on our form, and sets up handlers for each of the events, in the form of methods that begin with Add_.  We can use these to setup a handler, so that when an event is triggered, something cool happens.

    There are a metric pant load of events, for every element. You can see them by piping one of your objects into Get-Member like so:

      $WPFComputerName | Get-member Add* -MemberType Method -force
    All we have to do is add some code after the event we want to handle, and that's it. Sugar baby, SUGAR!
    All we have to do is add some code after the event we want to handle, and that’s it. Sugar baby, SUGAR!

    So, let’s say you want to run a chunk of code when the user moves over to another element in your form (which is called LosingFocus, which happens to me constantly during the day…hey I wonder what’s on reddit…). For example, let’s echo out the value of a box when a user moves away from the box.

      $WPFComputerNameBox.Add_LostFocus({Write-Host $WPFComputerName.Text})

    I’ll stage this in the context of a little mini GUI, here’s the XAML, and then below, the PowerShell code.

    <Grid Background="Azure">
            <Button x:Name="button" Content="Click Me" HorizontalAlignment="Left" Height="32" Margin="62,186,0,0" VerticalAlignment="Top" Width="106"/>
            <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="30" Margin="62,92,0,0" TextWrapping="Wrap" Text="Type stuff here" VerticalAlignment="Top" Width="106"/>
    
        </Grid>
    </Window>
    

    Method Code

    $WPFbutton.add_MouseEnter({Write-host 'Yay, the mouse is over me!'})
    $WPFbutton.Add_MouseLeave({Write-host 'he didnt click me...:('})
    
    $WPFTextBox.Add_TextChanged({Write-host "the user typed something, something like this $($WPFtextBox.Text)"})

    As I interact with my form, you can see the background updating in real time.

    RESUME HERE – Giving the Form some Smarts

    With that done, I’m now going to go back and set the tabs for tabs 2, 3 and 4 as disabled, so that the user will have to interact with tab 1 before proceeding. Do this by adding IsEnabled=”False” to each TabItem header, like this:

    <TabItem Header="System Info" IsEnabled="False">

    If you’re following along at home, your XAML should look something like this now:

    <Window x:Class="Tab_Me_baby_one_more_time.TabMe" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Tab_Me_baby_one_more_time" mc:Ignorable="d" Title="TabMe" Height="258.4" Width="486.4">
        <Grid>
            <TabControl x:Name="tabControl">
                <TabItem Header="ComputerName">
                    <Grid Background="#FF0B4A80">
                        <TextBlock TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="10,10,150.4,0" >
        This is the FoxDeploy official Awesome Tool.  To begin using this tool, first verify that a system if online, and then progress to any of the above tabs.
                        </TextBlock>
    
                        <TextBox x:Name="ComputerName" TextWrapping="Wrap" HorizontalAlignment="Left" Height="32" Margin="20,67,0,0" Text="TextBox" VerticalAlignment="Top" Width="135" FontSize="14.667"/>
                        <Button x:Name="Button" Content="Verify Online" HorizontalAlignment="Left" Margin="173,67,0,0" VerticalAlignment="Top" Width="93" Height="32"/>
                        <Image x:Name="image1" Stretch="UniformToFill" HorizontalAlignment="Left" Height="98" Margin="9,99,0,0" VerticalAlignment="Top" Width="245" Source="C:\Users\Stephen\Dropbox\Speaking\Demos\module 13\Foxdeploy_DEPLOY_large.png"/>
                    </Grid>
                </TabItem>
                <TabItem Header="System Info" IsEnabled="False">
                    <Grid Background="#FF0B4A80">
                        <Button x:Name="Load_diskinfo_button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
                        <ListView x:Name="disk_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="325" RenderTransformOrigin="0.498,0.169">
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="80"/>
                                    <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="80"/>
                                    <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="80"/>
                                    <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="80"/>
                                </GridView>
                            </ListView.View>
                        </ListView>
                    </Grid>
    
                </TabItem>
                <TabItem Header="ComputerName" IsEnabled="False">
                    <Grid Background="#FF0B4A80"/>
                </TabItem>
                <TabItem Header="Processes" IsEnabled="False">
                    <Grid Background="#FF0B4A80"/>
                </TabItem>
            </TabControl>
    
        </Grid>
    </Window>
    

    We’re going to plug some logic in at the bottom of our XAML/WPF Snippet. When the user launches this form, we want the $WPFComputerName.Text to default to the user’s computer name. When the user clicks the $WPFVerify button, we’re going to ping the system, if it replies, we’ll activate the rest of the tabs.

    When I said earlier that every object in our GUI has a slew of events, that includes the form (background) of our app itself.  We’ll use the Form’s own Add_Loaded({}) event handler to tell our form to run a snip of code when it finishes loading, which is a useful way to set a default value for a box, for instance.

    Below that, we’ll give our Verify button some code to run when it throws a click event (when the user clicks the button), via the .Add_Click({}) event handler method.  The code will try to ping the computer in the ComputerName box; if it replies, it will then enable each of the tabs for the user to click them by stepping through each item in the TabControl, and setting the ‘IsEnabled’ property of each to $True, to turn the lights on.

    $Form.Add_Loaded({$WPFComputerName.Text = $env:COMPUTERNAME})    
    
    $WPFVerify.Add_Click({if (Test-Connection $WPFComputerName.Text -Count 1 -Quiet){
        write-host "$($WPFComputerName.Text ) responded, unlocking"
        $WPFtabControl.Items[1..3] | % {$_.IsEnabled = $true}
        }
        else{
        write-host "$($WPFComputerName.Text ) did not respond, staying locked"
        $WPFtabControl.Items[1..3] | % {$_.IsEnabled = $false}
        }
    })
    

    Now when the form loads, the user has to interact with tab 1 before they can go on to the rest of the UI.

    User can't click these till they ping a system first
    User can’t click these till they ping a system first

    For our System / Disk Info panel, I’m liberally applying the principle of code reuse to recycle the code from Part II of this series.  There we covered how to instruct PowerShell to assign certain properties to certain columns in a GridView table, using Binding.  This code will grab the hard drive info, and display it in a table on tab II.

    $WPFLoad_diskinfo_button.Add_Click({
    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}}
                                                                     }
                                                                      
    
    Get-DiskInfo -computername $WPFComputerName.Text | % {$WPFdisk_listView.AddChild($_)}
    
    })
    
    

    With all of these changes in place, when the user provides a valid computer name, the other tabs will all unlock, then they can click the Get-DiskInfo button and…get some disk info.

    10

    Wrapping up

    Your next steps to flushing out the rest of the UI is to figure out how to apply the same principles from Tab II to the tabs for Services and Processes.  I’ve got you started here with the GUI for the Services Tab, it’s up to you to handle the GUI for the processes tab, and the logic for both.   If you pay close attention to one of the column headings in the Services Tab, you’ll find you have to think outside of the box to come up with the value I’m asking for there.

    Should you come up with something you’re particularly proud of, please sanitize your code (remove company info) and share it in the comments.  I’d recommend making a Gist or putting your code on GitHub or PasteBin, rather than dumping in 30KB of PowerShell and XAML below :)   I’m always surprised by the creativity my readers display, particularly when proving me wrong :p

    Complete XAML

    <Window x:Class="Tab_Me_baby_one_more_time.TabMe" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Tab_Me_baby_one_more_time" mc:Ignorable="d" Title="TabMe" Height="258.4" Width="486.4">
     <Grid>
     <TabControl x:Name="tabControl">
     <TabItem Header="ComputerName">
     <Grid Background="#FF0B4A80">
     <TextBlock TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="10,10,150.4,0" >
     This is the FoxDeploy official Awesome Tool. To begin using this tool, first verify that a system if online, and then progress to any of the above tabs.
     </TextBlock>
    
     <TextBox x:Name="ComputerName" TextWrapping="Wrap" HorizontalAlignment="Left" Height="32" Margin="20,67,0,0" Text="TextBox" VerticalAlignment="Top" Width="135" FontSize="14.667"/>
     <Button x:Name="Verify" Content="Verify Online" HorizontalAlignment="Left" Margin="173,67,0,0" VerticalAlignment="Top" Width="93" Height="32"/>
     <Image x:Name="image1" Stretch="UniformToFill" HorizontalAlignment="Left" Height="98" Margin="9,99,0,0" VerticalAlignment="Top" Width="245" Source="C:\Users\Stephen\Dropbox\Speaking\Demos\module 13\Foxdeploy_DEPLOY_large.png"/>
     </Grid>
     </TabItem>
     <TabItem Header="System Info" IsEnabled="False">
     <Grid Background="#FF0B4A80">
     <Button x:Name="Load_diskinfo_button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
     <ListView x:Name="disk_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="325" RenderTransformOrigin="0.498,0.169">
     <ListView.View>
     <GridView>
     <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="80"/>
     <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="80"/>
     <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="80"/>
     <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="80"/>
     </GridView>
     </ListView.View>
     </ListView>
     
     <Label x:Name="DiskLabel" Content="Disk info for system: " HorizontalAlignment="Left" Height="24" Margin="117,10,0,0" VerticalAlignment="Top" Width="197" Foreground="#FFFAFAFA"/>
     </Grid>
    
     </TabItem>
     <TabItem Header="Services" IsEnabled="False">
     <Grid Background="#FF0B4A80">
     <Button x:Name="Load_services" Content="Load Services" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
     <ListView x:Name="service_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="355">
     <ListView.View>
     <GridView>
     <GridViewColumn Header="Name" DisplayMemberBinding ="{Binding ServiceName}" Width="80"/>
     <GridViewColumn Header="DisplayName" DisplayMemberBinding ="{Binding 'DisplayName'}" Width="100"/>
     <GridViewColumn Header="Status" DisplayMemberBinding ="{Binding 'Status'}" Width="80"/>
     <GridViewColumn Header="AutoStart" DisplayMemberBinding ="{Binding 'AutoStart'}" Width="80"/>
     </GridView>
     </ListView.View>
     </ListView>
     <Button x:Name="Stop_service" Content="Stop Service" HorizontalAlignment="Left" Height="24" Margin="129,10,0,0" VerticalAlignment="Top" Width="77"/>
     <Button x:Name="Start_service" Content="Start Service" HorizontalAlignment="Left" Height="24" Margin="258,10,0,0" VerticalAlignment="Top" Width="77"/>
     </Grid>
     </TabItem>
     <TabItem Header="Processes" IsEnabled="False">
     <Grid Background="#FF0B4A80">
     <TextBlock Name="processes_text" TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="10,10,150.4,0" >
     Do something cool :)
     </TextBlock>
     </Grid>
     </TabItem>
     </TabControl>
    
     </Grid>
    </Window>
    

    My syntax highlighter was causing issues with the PowerShell code I posted. Instead, you’ll find the completed script here on the Github page for this post.

    For additional reading on Events, I’d recommend checking out Keith Hill’s Blog Post here, and June Blender’s excellent post here. Both helped me to finally understand how it is that this works, and I truly appreciate it.

    If you have any features you’d like to see me include next time, drop a comment here, hit me up on Twitter, or send me an e-mail at myFirstName At Foxdeploy.com. (You do have to replace MyFirstName with Stephen, by the way!)

    If you have a particular scenario and it’s interesting enough, I might write a whole post on it. So if you want to recreate the ConfigMgr 2012 UI in PowerShell XAML, let me know :)

    Who knows where we’ll go from here…

    !! GUI Progress bars? Must have!
    !! GUI Progress bars? Must have!

    Using PowerShell and oAuth

    $
    0
    0

    the oAuth Flow

    Like most of my posts here, I’m going to try to make something sound easy, when in reality I’ve spent months crying into my coffee trying to understand it. In truth, I’ve been trying to get oAuth to work for more than a year now.

    It all started with a simple goal, I just wanted to check my blog stats easily using WordPress’s REST API, and I wanted to do it from PowerShell, should be simple, right? WRONG

    My initial issue was that I didn’t want to understand oAuth, I just wanted to copy and paste some stuff and hope that it worked. I can tell you now that I’ve worked it out, it really isn’t that difficult, but knowing what’s happening will make it all much easier.

    What is oAuth?

    oAuth was made to solve a problem, that of sharing information between two different web services. For this reason, 99% of the examples and walk throughs that you’ll find online will be written in php, or use cUrl commands (an awesome *Nix command line utility), which is all well and good if you’re writing a server application, but not too useful to script things using a language like PowerShell. That being said, it is still possible, it just takes being a bit clever to get it to work.

    We need oAuth because a user may want to right click a file from Dropbox.com and post it on Facebook with one click, for instance. Or have their Twitter send a Tweet when they update their Blog on WordPress. Either way, oAuth is a crucial verification step when tying two services together, and it’s worth the time to spend learning how it works. Furthermore, most of the coolest REST APIs out there require you to authenticate using oAuth in order to even use them.  Now, let’s see what the steps are to get your application (or script) linked to a service that uses oAuth.

    The Core Steps of oAuth

    oAuth isn’t too complicated. Here’s what a normal oAuth exchange looks like. This happens the first time a user links two services (or instead links PowerShell or Python to a web service) and generally is a one time thing.

    Prompt the User for permission

    We’ll prompt the user with a login window, they’ll sign in and if they agree with the permissions we’re asking for, we receive an Access Code

    Exchange the Access Code for an Authorization Token

    We’ll submit that code in exchange for an authorization token. This is a one-time thing, as most auth tokens last quite a long time.

    Use the Authorization Token for future requests

    Now that we’ve got an Authorization Token, we can use this over and over to make action on behalf of our user.

    The last piece of info to know is that there are special URLs to use for each stage of the authorization process. For the rest of this post, assume I’m talking about WordPress.com, which requires you to use oAuth for authentication before you can start using their API.

    All of the code I’m referring to here can be found in the PSWordPress repo on GitHub. [link]. In my implementation of oAuth for WordPress, all of the following steps (except signing up for the API) are performed within the Connect-WordPress Account cmdlet, if you’d like to follow along.

    How to begin – Register for the API

    Any service which uses oAuth as the gateway to a REST API is going to have some mechanism in place for you to register for the API. If you want to use the WordPress.com API, for instance, you’ll register for it here.  Just to be clear, you’re not signing up for oAuth, rather oAuth is simply the mechanism that let’s your users trust your app or service with a bit of access to their account from something else, say, Twitter or FaceBook.

    Signing up for an API will normally involve you picking out a name for your project, providing a thumbnail image, and providing the URL of your application. Because the primary use case of oAuth is to allow services to talk to each other, even though we’ll be using PowerShell, we still must provide a URL. I’m using the URL of my blog, FoxDeploy.com, but you could pick literally any URL in the world.

    The key things you need to copy when you sign up are in this list below.  You’ll need to use them when you send the user off to the approval page, and then you’ll also need it when you exchange the access code for an authorization token:

    • ClientID, a numerical ID WordPress assigned us.
    • ClientSecret, a randomly generated strong password assigned to us. Generally, keep this secret. It’s not such a big deal for WordPress but some APIs will require payment, so this could be as costly as giving out a credit card number.
    • A list of URLs we need to query for different things

    oAuth Info

    Copy all of this stuff down somewhere safe (don’t put it in a GitHub project, for instance!)

    Get an Access Token

    The first real step in implementing oAuth, once you’ve got your Client details and the URLs, is to display a logon box to a user.

    This step is mostly prompting a user to provide you their credentials to a given service. We can grab the URL that we saw when we signed up for the API, and send the user there, providing a few values to represent your application/project, and then the user will login. When the user signs in, they’ll be redirected to the URL we specify, and the access token will be embedded at the end of the address they go to; we need to simply substring out the Access token and we’re set.

    Taking a look at the oAuth page for WordPress, we see where we need to provide our values to make the login box appear.

    AccessToken

    If you were to plop in values for clientID, and blogID, you’d see a standard oAuth sign in window, like this:

    oAuth Window

    Now, if you want to display a login box from PowerShell, you should use the cmdlet I’ve got here, Show-oAuthWindow.

    Showing a login window from PowerShell

    Show-oAuthWindow is where the magic happens. I found this example on the Hey! Scripting Guy Blog, written by Microsoft PowerShell PFE (Wow, talk about my dream job!) Chris Wu. It’s really beautifully simple.

    An assembly is loaded into PowerShell, to allow us to display some .net forms objects. Then the window size is specified, and we provide the URL for the browser window to go to (the ‘Authorize’ URL from my project screenshot above). We watch the process and instruct the window to close when the user is redirected to an address with Error= or code= in the URL.

     Function Show-OAuthWindow {
        Add-Type -AssemblyName System.Windows.Forms
     
        $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=440;Height=640}
        $web  = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=420;Height=600;Url=($url -f ($Scope -join "%20")) }
        $DocComp  = {
                $Global:uri = $web.Url.AbsoluteUri
                if ($Global:Uri -match "error=[^&amp;amp;amp;amp;amp;amp;]*|code=[^&amp;amp;amp;amp;amp;amp;]*") {$form.Close() }
        }
    
    

    If you were to run this code, you’d see a login box like this one:

    The user clicks Approve, and then our Access Code comes back in the form of some characters appended to the URL
    The user clicks Approve, and then our Access Code comes back in the form of some characters appended to the URL

    Assuming the user interacts with this successfully, they will be redirected to the $redirectURL, and our Access Code will be within the URL, looking something like the following: https://foxdeploy.com/?code=cw9hk1xG9k.  We’ll store the address in $uri, make it a global variable and then close the window, and we’re left with a $URI value that includes our access code.  We merely need to regex out the Access Code.

    $regex = '(?&amp;amp;amp;amp;amp;lt;=code=)(.*)(?=&amp;amp;amp;amp;amp;amp;)'
        $authCode  = ($uri | Select-string -pattern $regex).Matches[0].Value
        $global:accessCode = $accessCode
        Write-output "Received an accessCode, $accessCode"
    
    

    At this point, we have the Access Code, and it’s time to cash it in for an Authorization Code.

    Trade the Access Token to get an Auth Token

    Now that we have the time sensitive Access Token, it’s time to cash that in for a mostly-permanent authToken. Looking back at the WordPress API Docs again, they say the following (referring to our accessCode):

    One last step!
    One last step!

    They URL listed there is actually the Token URL we saw earlier when we sign up, and tell us to do a cUrl to that URL and add our (the ClientSecret), the project ID (Client ID) and your redirect URL, along with the Access Token we just got . Take all of these, present them as the body for one last Post and we’ll get back our Auth Token. Once we’ve done this, we don’t need to repeat the steps again until our Authorization Token expires.

    If you’re looking at my Connect-WordPressAccount cmdlet, we’re now up to line 46. The code calls Get-WordPressAuthToken (gitHub link), and passes in params it already had, like $clientSecret, $clientID and $blogURL. It also passes in $authCode, which we received in the last step.  I’ve excerpted this bit below:

    #Continuing in Connect-WordPress Account
            Get-WordPressAuthToken -ClientID $ClientID -clientSecret $clientSecret -blogURL $blogURL -authCode $authCode -Debug
    
            #store the token
            $password = ConvertTo-SecureString $accessToken -AsPlainText -Force
            $password | ConvertFrom-SecureString | Export-Clixml $configDir -Force
    
    

    Now, to look within Get-WordPressAuthToken, where the final bit of magic takes place:

    Function Get-WordPressAuthToken{
    [CmdletBinding()]
    param($ClientID,$blogURL,$clientSecret,$authCode)
    
        try { 
            $result = Invoke-RestMethod https://public-api.wordpress.com/oauth2/token -Method Post -Body @{client_id=$clientId; client_secret=$clientSecret; redirect_uri=$blogURL; grant_type="authorization_code"; code=$authCode} -ContentType "application/x-www-form-urlencoded" -ErrorAction STOP
         }
    
    

    We call the oAuth2/token endpoint, and just provide a list of parameters, including the Access Token we received when the user authorized our app. If this request worked, we’ll see something like the following.

    {
    "access_token": "YOUR_API_TOKEN",
    "blog_id": "blog ID",
    "blog_url": "blog url",
    "token_type": "bearer"
    }

    We simply need to take the Access Token and save it for future use. We’re done with authorizing now!

    Storing the Token Safely

    The user has now trusted us to partially act on their behalf for WordPress/whatever service.

    This token we have is the functional equivalent to them just handing us their computer logged in, with Chrome open and signed in as them on this service. Anything you can do as a person using the service, someone with your Auth Token could do as well. This means you need to find a safe place to store it.

    I opted to use the secure string cmdlets to store them safely within the user’s appdata folder, so if you’re looking at my own example, I use the system security API to safely store this info.  It can only be read (by default) by that user, or administrators of that PC, so it is fairly safe.  When the user loads any cmdlet from my PowerShell module, the variable is reloaded into $accessToken so that all of the PowerShell cmdlets that need the token ‘just work’.

        #store the token
            $password = ConvertTo-SecureString $accessToken -AsPlainText -Force
            $password | ConvertFrom-SecureString | Export-Clixml $configDir -Force
    
    
    Using THE token to actually query the API

    The final thing to do is to try this token out against one of the many, many endpoints available. The awesome thing about Rest APIs is that they almost always have some totally kick-butt documentation, including pages and pages of addresses we can query for nifty info. Here’s a snippet of just a few, listed on developer.wordpress.com/docs/api. I’ll pick one out, like /me

    append any of these to /rest/v1.1 and you're in busines
    append any of these to /rest/v1.1 and you’re in business

    oAuth me

    According to this, I just need to query the URL listed and provide my Authorization Token as a header.

    In PowerShell, this looks something like this:

    Invoke-RestMethod https://public-api.wordpress.com/rest/v1.1/me -Method Get -Headers @{"Authorization" = "Bearer $accessToken"}
    
    
    Getting data back from a REST API, awesome!
    Getting data back from a REST API, awesome!

    That’s really all there is to it!  The data will come back already serialized, in most cases, and you’re in business.

    Let’s make some cmdlets!

    At this point, all you need to do to make a slue of cmdlets is to look at the endpoints, find nifty ones, and then just setup functions that ask for the right info. This is the fruit of my labors, right here.

    
    Function Get-WordPressStats {
    [Cmdletbinding()]
    param(
        [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,
                       Position=0)]
                       [Alias("domainName")] $ID,
                       $accessToken=$Global:accessToken)
    
    
    Invoke-RestMethod https://public-api.wordpress.com/rest/v1.1/sites/$ID/stats -Method Get `
       -Headers @{"Authorization" = "Bearer $accessToken"}  | 
            Select-object -ExpandProperty Stats |Select-Object Views*,Visit*
    
    }
    
    
    get-wordpressStats
    Let’s REST

    I hope you liked this post. I’m very happy to have finally worked out an approach that makes sense to me for oAuth. If you’ve got some specific requests or some REST APIs that are giving you fits, let me know and I’ll do my best to help you figure it out!

    Sources

    For helping me to understand how to present parameters for oAuth

    WordPress Reference for API, with actually really good documentation


    Hyper-V on Windows 10, how to fix a broken VHD

    $
    0
    0

    I’m running Windows 10 w/ Hyper-V on my home lab pc, and found a strange Hyper-V error this week.

    One of the core things you shouldn’t do with differencing disks is to ever, ever move or edit the parent disk, or else you can break the child disk.  I added some more storage and moved my VHDs around to an all SSD Raid, then found that I’d broken the chain of VHDs for one of my VMs.

    When trying to start the VM, this is the error you’ll see.

    Img 1
    The chain of virtual hard disks is broken. The system cannot locate the parent virtual hard disk for the differencing disk.

    Normally, one runs ‘Inspect Disk’ from Hyper-V to locate the parent disk of a child VHD, which will fix the chain of differencing and allow your VMs to access the disks again.  However on Windows 10, clicking ‘Inspect Disk’ will result in Hyper-V throwing a fatal error.

    There was a problem with one of the command line parameters. Either 'BEHEMOTH' could not be found, or 'X:\Virtual Machines\Virtual Hard Disks\VM01.vhdx' is not a valid path. Property 'MaxInternalSize' does not exist in class 'Msvm_VirtualHardDiskSettingData'.
    There was a problem with one of the command line parameters. Either ‘BEHEMOTH’ could not be found, or ‘X:\Virtual Machines\Virtual Hard Disks\VM01.vhdx’ is not a valid path.
    Property ‘MaxInternalSize’ does not exist in class ‘Msvm_VirtualHardDiskSettingData’.

    The path is valid and exists. I’ve found a workaround, that of using PowerShell and the Hyper-V module to run Set-VHD, like so:

    set-vhd -Path ‘X:\Virtual Machines\Virtual Hard Disks\VM01.vhdx’ `
    -ParentPath “X:\Server2012Template\Virtual Hard Disks\Server2012Template.vhdx”

    Anyway, this is a bit of a bugger, and I’ve alerted authorities in Redmond, so we should see a fix soon :)


    Windows vs Intel Raid Performance Smackdown

    $
    0
    0

    Update: Turns out I was pretty wrong about Storage Spaces and have been totally schooled and corrected about them here, on Reddit and at Redmond.  I’ll be revising this post heavily, adding some iometer numbers.  Stay tuned!

    In this post, I’ll share my results comparing hardware to software RAID on modern Windows 10, then to take things up a notch, I’ll see how a Storage Spaces volume with Parity (maybe Microsoft is charged money for using the word ‘Raid’, because they avoid that word like the plague!) compares to the hardware solution (Spoilers: it ain’t pretty) in pretty much every configuration I could think of.

    I’ve had a lot of issues in the FoxDeploy lab recently. Two months ago, I had a number of SSDs in a Storage pool on Server 2012 R2, then decided I should go to the desktop experience, and moved to Windows 10.  Let’s call this rebuild #1.

    Since then, I’ve had an SSD fail in a Windows Mirror, resulting in a total loss of VMs, as I didn’t have backup working.  Rebuild #2

    Next, I decided to take three drives, two physical SSDs and a chunk of unpartioned space from a six year old spinner, and put them in a Raid-5.

    As it turns out, this kills the drive.  Goodbye, Big Betty, I will miss you. Rebuild #3.

    At this point, I had it with these janky solutions.  I’ve recently come into possession of some new SSD drives, and this time, I’m going to do it right, damn it! I wanted to finally have some resiliency in case one of my drives died.  But along the way, I needed to know how it would perform.

    Findings – Raid 0 Testing

    For starters, here’s our benchmark.  It’s a single OCZ 120 GB SSD disk, connected via 3GBPS sata.

    Single disk, sata 3
    singledrive
    Single Disk Performance

    Not bad, very respectable. Now, let’s see what happens when I use Windows to take two of these and stripe them. This is also called Raid 0, as in ‘you have zero parity and are totally hosed if one of these drives dies’.

    Expectations: You gain the full capacity of both drives and speed to spare, as data is randomly written across drives.  Speed should be roughly double that of a single drive.  However, if one dies, you are up a creek, because you will not be able to recover your data. You’ll tend to get ~double the read and write performance when striping.

    two disks, SATA 3, windows raid 0
    twodrivesWindowsRaid0
    WIndows Raid 0

    If we compare the two, we see roughly double the performance as well.  Windows raid is achieving parity via the OS. Since data striping (putting chunks of data on the disk) is handled by the operating system, there is a potential for disk performance to suffer when the rest of the OS is under heavy load.  Microsoft has had years to work out the errors and speed things up though, so don’t let this stop you if your motherboard doesn’t support Raid.

    Now, Let’s see how Windows Raid-0 stacks up to actual hardware raid.

    TWO DISKS, SATA 3, Hardware RAID 0
    twodrivesIntelRaid0
    Hardware Raid 0

    To put those side-by-side, here’s the difference you can expect when comparing hardware Raid-0 to Software Raid-0.

    SIDE BY SIDE, INTEL % CHANGE VS SOFTWARE RAID 0
    Raid0 testing
    Intel Performance Increase over Microsoft

    Winner – Intel
    There is quite a performance edge to gain by using a hardware controller, even nowadays. I was very surprised to see such a disparity here.  In every regard there were noticeable speed gains, at least on paper.

    Findings – Raid 1 testing

    Now, these big numbers were very fun, but as I’m doing this to test my performance while operating safely with parity, I needed to see what would happen when actually using mirroring.

    Expectations – With Raid 1, you should receive roughly double read speed, while writes will suffer, because of the need to create and distribute parity sets across disks. You lose the whole capacity of one drive, but have a full backup of your active volume in case it dies. Nothing is safer than Raid 1!
    First, I tested in Windows for Raid 1, also known as a ‘Mirrored Set’.

    TWO DISKS, SATA 3, WINDOWS RAID 1
    Windows Raid 1

    Wow, the reads are still good, but boy does the writes take a huge faceplant. Writes were roughly half of the write speed for a single drive, which was surprisingly bad, in my opinion.  Functionally, this makes sense, as the OS has to track syncing writes to two different disks.  Who knew that it would levy such a heavy penalty though.

    Now, to compare this to using dedicated Raid hardware.

    TWO DISKS, SATA 3, hardware RAID 1
    twodrivesIntelRaid1
    Intel Raid 1

    While the Intel raid controller blows the Software Raid out of the water on sequential reads, surprisingly the Windows software Raid was better in nearly every other respect. It seems that no matter if you use a hardware or a software Raid controller, you should expect to lose performance when you’re duplicating every write, which makes sense.

    We can compare this to our single disk performance though, and see something else happening.

    Side by side, intel % change vs software raid 1
    Raid1 testing
    Intel % increase over Microsoft Software Raid

    Winner – Windows
    If you need super fast sequential reads, like you’re working with a single big file that happens to be on one contiguous area of disk, if you could control that, then you’d want the Intel Controller. If not, Windows looks to be good enough, and takes the lead in most categories.

    Findings – Raid 5 testing

    Raid 5 was the defacto standard for parity across volumes smaller than a TB. Now, it’s been replaced with other Raid and parity options, but it’s still probably the most prevalent way of safely allowing for disk failures without a tremendous speed loss. With Raid-5, you lose the space of one drive.  With that being said, you can get some beefy performance, and have parity!

    Raid 5 in Windows 10, some notes before we begin

    In this round, we’ll be leaving the Disk Management wizard for Windows and instead go into the control panel to create a Storage Space!  Previously, there was no way for end user running home or PRO Windows to do make a parity volume, which reserved that feature for Windows Server users only.  Storage Spaces is Microsoft’s new, much easier to use approach to Disk Management with loads of options.

    You can configure this any way you want to! Just spend a LOT of time reading documentation.
    You can configure this any way you want to! Just spend a LOT of time reading documentation.

    While Microsoft doesn’t call it ‘Raid 5’, it’s possible using Storage Spaces to create a Parity Pool, which will effectively achieve the same results.

    Raid-5 by any other name. Double Parity would be Raid-10!
    Raid-5 by any other name. Double Parity would be Raid-10!

    It should be noted that I had to tune this array to get these results.  First off, a default Storage Spaces drive will be created with a Write-back Cache of 1 GB.  It should also be said that there IS a distinct performance penalty at play here.  In order to play things safe, there is some mandatory disk journaling involved here, meaning that your writes aren’t going to be cached very much.  Instead, the storage subsystem will wait for confirmation of all writes, and it does so VERY conservatively.

    It’s not uncommon to see R/WR delays into the tens of thousands of milliseconds while writes are performed.  You can somewhat minimize this behavior by running these two PowerShell cmdlets.  The first GREATLY boosts the write cache amount, which I’ve seen to boost performance, while the second cmdlet specifies that there is Power protection (like a UPS).  To quote the help documentation ‘If you specify a value of $True for this parameter, the storage pool does not perform flush operations, and the pool removes write-through attributes from commands.’  It also helps.

    Get-StoragePool | select -First 1 | set-storagepool -WriteCacheSizeDefault 8GB
    Get-StoragePool -FriendlyName “Storage Pool” | Set-StoragePool -IsPowerProtected $true

    Now, after that length pre-amble, let’s hop to it.

    three DISKS, SATA 3, HARDWARE RAID 5
    ThreedrivesIntelRaid5
    Hardware Raid 5

    As expected from hardware Raid 5, we receive roughly 100% read speed from all drives, for some truly mammoth read speeds.  Writes hang out around 2/3rd’s of the total write speed for all drives, making this the power-user’s option.  It should be noted that I had to specify a custom disk policy for this Raid 5 volume to enable write caching and disable flush operations.  This is definitely a power-mode configuration, but depends on battery backup to ensure there is no data loss.

    Be warned, you will lose data if you have a power outtage. Only check these if you've got reliable UPS in place.
    Be warned, you will lose data if you have a power outtage. Only check these if you’ve got reliable UPS in place.

    If you’re using Raid 5, you’re probably data loss averse, so only specify these settings if you’ve actually got a battery backup.

    Now, onto the final option, Storage Spaces with Parity.  This is super secure for your data, however…writes…well…

    THREE DISKS, SATA 3, Storage spaces parity volume (raid 5)
    Storage Spaces Journaling aggressively protects data, but at a cost
    Storage Spaces Journaling aggressively protects data, but at a cost

    I reached out to some peers for help, to see if I was mistuning this array, because, frankly, damn.  

    According to Aidan Finn, Microsoft MVP of Datacenter and Hyper-V, I’m doing it wrong.  This is not meant for performance, nor is it a good idea for storing VHD files.  You should be using parity volumes as a replacement for a super expensive NetApp or LeftHand SAN Solution, with wonderful configurability for hundreds of potential configuration.  You use this to save money for your archival storage, but shouldn’t expect to get equal performance.

    Still, the numbers are pretty shocking.  Here’s Storage Spaces Parity volume compared to a single drive, and to a Raid 5 Volume.

    StorageSpace v Everything Else
    Great for Reads versus a single drive, but writes suffer incredibly.

    Winner – Intel

    Now, Storage Spaces can do things you cannot do using a simple Intel Raid Controller.  For instance, you could easily take four full JBOD (Just a bunch of disk) arrays, filled with 8 TB drives and also slap in a stack of PCI-e or SSD storage to create a wonderfully resilient storage system with excellent performance.

    But it’s not the answer to every problem.  In this case, the Intel Raid Controller was the clear winner, absolutely smoking the Storage Spaces Controller.  This graph says it all

    12,000% improvement in writes is mindblowing
    12,000% improvement in writes is mindblowing

    In Conclusion

    If you want maximum performance for a small number of disks, need some basic parity support, and don’t have more than 13TB in a volume, and you have a hardware Raid Controller, then you should definitely use hardware Raid 5 over storage spaces.

    You need maximum speed and will backup your data – Use Raid 0 and backup data

    You need maximum speed and some resilience to disk failure – Use Raid 1 or Raid 5

    You need to archive monstrous amounts of data across insane configurations of disk arrays – Use Storage Spaces.

    Finally, some random charts.

    raid 5
    Intel RAID-5 performance over Single Disk

    raid 1 vs single drive

    Did I do it wrong?  Am I missing something?  Correct the author by e-mailing me at Stephen@foxdeploy.com, or commenting here.

     


    Viewing all 109 articles
    Browse latest View live