ConfigMgr OSD Device Naming

For whatever reasons, this still seems to be an interesting and ongoing topic of discussion/debate/argument/angst/drinking, etc.  I’m talking about assigning names to computers while they’re being imaged or refreshed.  There are quite a few common scenarios, and an unknown variety of less-common scenarios, I’m sure.  Some embrace this and some despise it.  The views are all over the place it seems.  But regardless, as a consultant, I’m very often tasked with advising (okay: consulting) customers on options for either fully or partially automating the process.

Personally, I approach all automation scenarios with the same view: Explain why you need it.  Very often, automation is simply taking over from a poorly designed manual process.  As an old colleague of mine once said (and I repeat ad nauseum) “If you automate a broken process, you can only get an automated broken process“.  It’s true.  If the customer is receptive to a discussion about alternatives, it always produces a more positive outcome.  So far, at least.

Anyhow, I wanted to lay out some common scenarios and examples for handling them.  These are based on the use of either Microsoft System Center Configuration Manager (aka ConfigMgr, aka SCCM), or Microsoft Deployment Toolkit (aka MDT).  I am by no means implying these are the ONLY way to accomplish each scenario.  They are provided simply as ONE way out of many, but which I’ve found offer the best benefit for the least effort and risk.  You are free to roll your eyes and call me a dumbass, that’s fine, because I plug my ears anyway.

Manual: Stick shift (The Exploding Space Shuttle Method)

This method requires a human to manually enter the desired name to assign to the device.  It’s the easiest/quickest method to implement.

For ConfigMgr environments, the quickest implementation is by assigning “OSDComputerName” Collection Variable to a device collection.  For bare metal (e.g. new) machines, that is often the “Unknown Computers” collection, but for refresh/reimage scenarios it’s whatever device collection is being targeted by the relevant task sequence.

Pro – easy, fast, cheap

Con – prone to human error

Fully-Automatic: Serial Number

Since Windows 10 still imposes a limit of 15 characters, with restrictions on certain alphanumeric characters, this method often includes some checking/filtering to insure that the process doesn’t swallow a grenade and make a mess.  This is less common today with physical machines, but often occurs with virtualized environments due to differing serial number formats.  This example uses the BIOS serial number as the machine name.  If the serial number is longer than 15 characters, only the last 15 characters are used.  So “CNU1234567891234” would become “NU1234567891234”.

# Set-ComputerNameAutoSN.ps1
$snmax = 15
$csn = (Get-WmiObject -Class Win32_SystemEnclosure).SerialNumber
if ($csn.Count -gt 1) { $csn = $csn[0] }
if ($csn.Length -gt $snmax) {
  $csn = $csn.Substring($csn.Length - $snmax)
}
try {
  $tsenv = New-Object -ComObject Microsoft.SMS.TSEnvironment
  $tsenv.Value("OSDComputerName") = $csn
  Write-Output 0
}
catch {
  Write-Output "not running in a task sequence environment"
}

Note: You might be wondering why the “if ($csn.Count -gt 1)…” stuff.  That’s in case the device is sitting in a dock or connected via an e-dock or dongle, which often causes WMI queries to return an array instead of a single integer for things like ChassisTypes and so on.  Checking the count of values assigned to the variable allows for retrieving just the first element in the array.

Full-Automatic: Form-Factor + Serial Number

This is a variation on the preceding example, whereby a prefix (or sometimes a suffix) is added to the name to identify a general form, such as “D” for desktop, and “L” for laptop, and so on.  This example would take a ChassisType value of 3 and the last 10 characters (arbitrary, not required) of the Serial Number of “CNU1234567891234” and concatenate these into “D4567891234”

# Set-ComputerNameAutoFormSN.ps1
$snmax = 10
$csn = (Get-WmiObject -Class Win32_SystemEnclosure).SerialNumber
$cff = (Get-WmiObject -Class Win32_SystemEnclosure).ChassisTypes
if ($csn.Count -gt 1) { $csn = $csn[0] }
if ($cff.Count -gt 1) { $cff = $cff[0] }
if ($csn.Length -gt $snmax) {
  $csn = $csn.Substring($csn.Length - $snmax)
}
switch ($cff) {
   3 { $ff = 'D'; break }
   4 { $ff = 'D'; break }
   5 { $ff = 'D'; break }
   6 { $ff = 'D'; break }
   7 { $ff = 'D'; break }
   8 { $ff = 'L'; break }
   9 { $ff = 'L'; break }
  10 { $ff = 'L'; break }
  11 { $ff = 'L'; break }
  14 { $ff = 'L'; break }
  default { $ff = 'X'; break }
}
$newname = $ff+'-'+$csn
try {
  $tsenv = New-Object -ComObject Microsoft.SMS.TSEnvironment
  $tsenv.Value("OSDComputerName") = $newname
  Write-Output 0
}
catch {
  Write-Output "you forgot to flush the toilet"
}

Semi-Automatic: Location Code + Form Factor + Serial Number

This one is based on a scenario where all devices are imaged at a single location, and shipped out to other locations, whereby the customer wishes to place the AD computer account into an associated location-based Organizational Unit (OU) during the imaging process.  This can be done using User-Driven Interface (UDI) forms, or by script.  I prefer script because I’m more comfortable with it and there are far fewer moving parts to contend with.  That said, there are several ways to do this:

Option 1 – SCCM Collection Variable

A collection variable can be assigned to the target collection in order to prompt for a location identifier.  This could be a number, a name, or an abbreviation, it doesn’t really matter since it’s just a textbox form input.

Option 2 – MDT / SCCM Task Sequence Script

Some flavors of this involve copying the ServiceUI.exe component from the MDT installation into the script package, so that ServiceUI.exe can be invoked to suppress the task sequence progress UI and allow custom script GUI forms to display properly.  I did it this way for a long time, until I ran across this really nice script from John Warnken *here*.  After some (minor) tweaking it can easily be adapted to almost any need.  The example below prompts the technician for a 3-character Location Code and then concatenates the device name using [Location]+[FormFactor]+[SerialNumber] up to 15 characters, so the [SerialNumber] value is truncated to the last 10 characters (max).

<#
Set-ComputerNameFormLocSN.ps1
.SYNOPSIS 
Displays a gui prompt for a computername usable in a SCCM OSD Task Sequence. 
 
.PARAMETER testing 
The is a switch parameter that is used to test the script outside of a SCCM Task Sequence. 
If this -testing is used the script will not load the OSD objects that are only present while a task sequence is running. 
instead it will use write-output to display the selection. 
 
.EXAMPLE 
powershell -executionpolicy Bypass -file .\Set-ComputerNameLocationPrompt.ps1 
 
.EXAMPLE 
powershell -file .\Set-ComputerNameLocationPrompt.ps1 -testing 
 
.NOTES 
This is a very simple version of a OSD prompt for a computername. You can add extra validation to the computer name, for example a regular expression test 
to ensure it meets standard form used in your environment. Addtional form object can be added to other options that you may want to set 
task sequence variables for. Also as a simple example, I just added the xaml for the wpf form as a variable in the script. You have the option of storing it in 
a external file if your form gets complex.

.Author 
Jonathan Warnken - jon.warnken (at) gmail (dot) com 
https://gallery.technet.microsoft.com/scriptcenter/Prompt-for-a-computername-6f99fa67
.Hallucinator
Skatterbrainz - @skatterbrainzz
#> 
[CmdletBinding()] 
param( 
  [parameter(Mandatory=$False)]
    [switch] $Testing
)
$snmax = 10
if(!$Testing){
  # this section provides the gluten-free, high-protein alternative to ServiceUI.exe
  $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment  
  $tsui  = New-Object -COMObject Microsoft.SMS.TSProgressUI  
  $OSDComputername = $tsenv.Value("OSDComputername")
  $tsui.CloseProgressDialog() 
}

# query machine serial number and chassis type from WMI
$csn = Get-WmiObject -Class Win32_SystemEnclosure | Select-Object -ExpandProperty SerialNumber
$cff = Get-WmiObject -Class Win32_SystemEnclosure | Select-Object -ExpandProperty ChassisTypes

# check if return values are an array and if true, then only use the first element
if ($csn.Count -gt 1) { $csn = $csn[0] }
if ($cff.Count -gt 1) { $cff = $cff[0] }

# if serial number is longer than 10 chars, get last 10 chars only
if ($csn.Length -gt $snmax) {
  $csn = $csn.Substring($csn.Length - $snmax)
}
# derive form factor code from chassis type code
switch ($cff) {
   3 { $ff = 'D'; break }
   4 { $ff = 'D'; break }
   5 { $ff = 'D'; break }
   6 { $ff = 'D'; break }
   7 { $ff = 'D'; break }
   8 { $ff = 'L'; break }
   9 { $ff = 'L'; break }
  10 { $ff = 'L'; break }
  11 { $ff = 'L'; break }
  14 { $ff = 'L'; break }
  default { $ff = 'X'; break }
}
[xml]$XAML = @' 
<Window 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  Title="SCCM OSD Computername" Height="154" Width="425" Topmost="True" WindowStyle="ToolWindow"> 
  <Grid> 
    <Label Name="Computername_label" Content="Location:" HorizontalAlignment="Left" Height="27" Margin="0,10,0,0" VerticalAlignment="Top" Width="241"/> 
    <TextBox Name="LocationCode_text" HorizontalAlignment="Left" Height="27" Margin="146,10,0,0" TextWrapping="Wrap" Text=" " VerticalAlignment="Top" Width="220"/> 
    <Button Name="Continue_button" Content="Continue" HorizontalAlignment="Left" Margin="201,62,0,0" VerticalAlignment="Top" Width="75"/> 
  </Grid> 
</Window> 
'@ 
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
#Read XAML 
$reader=(New-Object System.Xml.XmlNodeReader $xaml) 
$Form=[Windows.Markup.XamlReader]::Load( $reader )

# add form objects as script variables 
$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)}

# set the default value for the form textbox
$LocationCode_text.Text = 'STL'

# assign event handler to the "Continue" button
$Continue_button.add_Click({ 
  $Script:LocationCode = $LocationCode_text.Text.ToString()
  $Form.Close() 
})
# display the form for user input
$Form.ShowDialog() | Out-Null

# after form is closed, concatenate computer name from input values and other variables
$computername = $Script:LocationCode+$ff+$csn

if (!$Testing) {
  $tsenv.Value("OSDComputername") = $computername 
  Write-Output " OSDComputername set to $($tsenv.value("OSDComputername"))" 
}
else { 
  Write-Output " OSDComputername would be set to $computername" 
}

Pro – works with PXE and offline media

Con – some scripting and testing required

Fully Automatic: Location Code + Form Factor + Serial Number

This method relies on something available from the environment upon which to determine the “location”.  For MDT this is often using the CustomSettings.ini with the IP Gateway address.  Using a logical if/then you can construct a logic mapping of IP gateway = Location or location Code.

Option 1 – MDT Deployment Share Configuration

This one uses a CustomSettings.ini which reads the IPv4 Gateway address and the Serial Number to concatenate the resultant device name.  For a better example of this and a much, much better explanation by the one-and-only Mikael Nystrom, please read this first.

[Settings]
Priority=Init, ByDesktop, ByLaptop, DefaultGateway, Default 
Properties=ComputerLocationName, ComputerTypeName, ComputerSerialNumber

[Init]
ComputerSerialNumber=#Right("%SerialNumber%",10)#

[ByLaptop]
SubSection=Laptop-%IsLaptop%
ComputerTypeName=L

[ByDesktop]
SubSection=Desktop-%IsDesktop%
ComputerTypeName=D

[DefaultGateway]
192.168.1.1=Norfolk
192.168.2.1=Chicago
192.168.3.1=NewYork

[Norfolk]
ComputerLocationName=NOR

[Chicago]
ComputerLocationName=CHI

[NewYork]
ComputerLocationName=NYC

[Default]
OSInstall=Y
ComputerLocationName=XXX
ComputerTypeName=X
OSDComputerName=%ComputerLocationName%%ComputerTypeName%%ComputerSerialNumber%
SkipCapture=YES
SkipAdminPassword=YES
SkipProductKey=YES
SkipComputerBackup=YES
SkipBitLocker=YES

Using this sample INI configuration, a laptop device with Serial Number “CNU1234567891234” connected to IP gateway 192.168.3.1 would end up with an assigned name of “NYCL4567891234”.  If the DefaultGateway is not defined in the list, it will default to location code “XXX”.  If the form factor cannot determine Desktop or Laptop it will default to “X”.  So, using the same serial number value it would end up as “XXXX4567891234”.

Option 2 – SCCM Task Sequence

This example adapts the semi-automatic example above, but replaces the GUI prompt with a check for the IP gateway.

# Set-ComputerNameAutoLocIPFormFactorSN.ps1
$csn = (Get-WmiObject -Class Win32_SystemEnclosure).SerialNumber
$cff = (Get-WmiObject -Class Win32_SystemEnclosure).ChassisTypes
if ($csn.Count -gt 1) { $csn = $csn[0] }
if ($cff.Count -gt 1) { $cff = $cff[0] }
if ($csn.Length -gt 15) {
  $csn = $csn.Substring($csn.Length - 15)
}
$gwa = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where {$_.IPEnabled -eq $True}).DefaultIPGateway
switch ($gwa) {
  '192.168.1.1' {
    $location = 'Norfolk'
    $loccode = 'NOR'
    break
  }
  '192.168.2.1' {
    $location = 'Chicago'
    $loccode = 'CHI'
    break
  }
  default {
    $location = 'NewYork'
    $loccode = 'NYC'
    break
  }
}
switch ($cff) {
   3 { $ff = 'D'; break }
   4 { $ff = 'D'; break }
   5 { $ff = 'D'; break }
   6 { $ff = 'D'; break }
   7 { $ff = 'D'; break }
   8 { $ff = 'L'; break }
   9 { $ff = 'L'; break }
  10 { $ff = 'L'; break }
  11 { $ff = 'L'; break }
  14 { $ff = 'L'; break }
  default { $ff = 'X'; break }
}
$newName = $loccode+$ff+$csn
try {
  $tsenv = New-Object -ComObject Microsoft.SMS.TSEnvironment
  $tsenv.Value("OSDComputerName") = $newName
  Write-Output 0
}
catch {
  Write-Output "not running in a task sequence environment"
}

Option 3 – SCCM Task Sequence + Collection Variable

This example came from a project where the customer had created a device collection for each branch location, which had a Collection Variable “DeviceLoc” assigned, and having deployed a refresh task sequence to each.  So in this scenario, they wanted to merge the script with the collection variable to derive the new name.  Is this ideal?  Not for most people, but due to time constraints it had to work.

$csn = (Get-WmiObject -Class Win32_SystemEnclosure).SerialNumber
$cff = (Get-WmiObject -Class Win32_SystemEnclosure).ChassisTypes
if ($csn.Count -gt 1) { $csn = $csn[0] }
if ($cff.Count -gt 1) { $cff = $cff[0] }
if ($csn.Length -gt 15) {
  $csn = $csn.Substring($csn.Length - 15)
}
$gwa = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | Where {$_.IPEnabled -eq $True}).DefaultIPGateway
switch ($gwa) {
  '192.168.1.1' {
    $location = 'Norfolk'
    $loccode = 'NOR'
    break
  }
  '192.168.2.1' {
    $location = 'Chicago'
    $loccode = 'CHI'
    break
  }
  default {
    $location = 'NewYork'
    $loccode = 'NYC'
    break
  }
}
switch ($cff) {
   3 { $ff = 'D'; break }
   4 { $ff = 'D'; break }
   5 { $ff = 'D'; break }
   6 { $ff = 'D'; break }
   7 { $ff = 'D'; break }
   8 { $ff = 'L'; break }
   9 { $ff = 'L'; break }
  10 { $ff = 'L'; break }
  11 { $ff = 'L'; break }
  14 { $ff = 'L'; break }
  default { $ff = 'X'; $ousub = ''; break }
}
try {
  $tsenv = New-Object -ComObject Microsoft.SMS.TSEnvironment
  $loc = $tsenv.Value("DeviceLoc")
  if ([string]::IsNullOrEmpty($loc)) {
    $newName = 'CRP'+$ff+$csn
  }
  else {
    $newName = $loc+$ff+$csn
  }
  $tsenv.Value("OSDComputerName") = $newName
  Write-Output 0
}
catch {
  Write-Output "not running in a task sequence environment"
}

Conclusion / Summary

This is only a small sample of all the variations and scenarios many of us encounter.  Other variants include setting the Organizational Unit (OU) path, adding the device to domain security groups, and so on.  I would like to thank John Warnken for the script code he posted on TechNet, which makes it so much easier to deal with the progress bar UI when using custom forms.

I’d also like to say that you shouldn’t look at these and think you need to immediately put them into use.  Test labs are one thing.  But production requires serious review and planning to determine what really makes the most sense.  For example, a lot of customers I work with have persistently used a location code for device names, but when they really assess the business environment, they find many devices roam to different locations, or don’t really stay in any “official” location (home, hotel, on the road, etc.)  And when pressed to explain how the location code provides real value, many don’t have a rationale besides, “it’s how we’ve always done it”.  So think it through.  Ask if you really need to do something like this and if so, why.  Then work on the how aspects.

Do you have other/better examples you’d like to share?  Post a comment below.  Thank you for reading!

Advertisements

ConfigMgr – 2 Minute Microwave Style

Genesis – I posted a tweet about someone I know getting stressed at learning Configuration Manager in order to manage 50 Windows devices.  All desktops.  The background is basically that his company had planned on 1000 devices running Windows.  But the end-users, who wield more purchasing power, opted to buy mostly Macbooks.  So the total Windows device count was capped at 50, BUT…. they already approved the purchase of ConfigMgr.  It’s worth noting that the end-users also purchases JAMF (formerly Casper) and set it up in their own secret underground lab, complete with a diabolical German scientist in a white lab coat.  Ok.  That last part isn’t really true, but the JAMF part is true.

So, the “discussion” slid into “okay mr. smarty-pants skatter-turd-brainz, what would you want in a ‘perfect’ ConfigMgr world to address such a scenario?” (again, I’m paraphrasing a bit here)

MC DJam, aka DJammer, aka David the Master ConfigMaster Meister of ConfigMgr, popped some thermal verbals in front of the house and the room went Helen Keller (that means quiet and dark, but please don’t be offended, just stay with me I promise this will make sense soon…)

Yes, I’ve had a few beers.  Full disclosure.  I had to switch to water and allow time for the electric shock paddles to bring my puny brain back online.  That was followed by a brief gasp,”oh shit?! what have I started now?”  Then some breathing exercises and knuckle crackings and now, back to the program…

So, Ryan Ephgrave (aka @EphingPosh) stepped in and dropped some mic bombs of his own.

And just like having kids, the whole thing got out ahead of me way too quick.

So, I agree with Ryan, who also added a few other suggestions like IIS logs, Chocolatey package deployments (dammit – I was hoping to beat him to that one).

So the main thing about this was that this person (no names) is entirely new to ConfigMgr.  Never seen it before, and only gets to spend a small portion of their daily/weekly time with it, due to concurrent job functions.  This is becoming more and more common everywhere I go, and I’ve blogged ad nauseum about it many times (e.g. “role compression”)

What do most small shop admins complain about?

  1. Inventory reporting
  2. Remote management tools
  3. Deploy applications
  4. Deploy updates
  5. Imaging
  6. Customizable / Extendable

These are the top (6) regardless of being ConfigMgr, LANdesk, Kace, Altiris, Solarwinds, or any other product.  All of them seem to handle most of the first 4 pretty well, with varying levels of learning and effort.  But Imaging is entirely more flexible and capable with ConfigMgr (or MDT) than any of the others I’ve seen (Acronis, Ghost, etc. etc. etc.)

ConfigMgr does an outstanding job of all 6 (even though I might bitch about number 6 in private sometimes, it is improving).  ConfigMgr is also old as dirt and battle-tested.  It scales to very large demands, and has a strong community base to back it up in all kinds of ways.  In some respects it reminds me of the years I spent with AutoCAD and Autodesk communities and the ecosystems that developed around that, but that’s another story for another time.

The challenge tends to come from just a few areas:

  1. Cost and Licensing – ConfigMgr is still aimed at medium-to-large scale customers.  The EA folks with Software Assurance, are most often interested and courted into buying it.  Some would disagree, but I set my beer mug down and calmly say “Walk into any major corporate IT office and ask who knows about ConfigMgr.  Then walk into a dentist office, car dealership, or small school system and ask that same question.”  I bet you get a different response.
  2. Complexity – ConfigMgr makes no bones about what it aims to do.  The product sprung from years of “Microsoft never lets me do what I want to manage my devices” (say that with a nasally whiny tone for optimum effect).  Microsoft responded “Here you go bitch.  A million miles of rope to hang yourself.  Enjoy!”  It’s an adjustable wrench filled with adjustable wrenches, because it was designed to be the go-to toolset for almost any environment.  And it’s still evolving today (faster than ever by the way)
  3. Administration – Anyone who’s worked with ConfigMgr knows it’s not really a “part-time” job.  But that’s okay.  It’s part of the “complexity” side-effect.  And rarely are two environments identical enough to make it cookie cutter.  That’s okay too.  Microsoft didn’t try to shoehorn us into “one way”, but said “here’s fifty ways, you choose“.  The more devices you manage with it, the more time and staff it often demands in order to do it justice.  I know plenty of environments that have scaled to the point of having dedicated staff for parts of it like App deployments, Patch Management, Imaging and even Reporting.

None of these are noted with the intention of being negative.  They are realities.  It’s like saying an NHRA dragster is loud and fast.  It’s supposed to be.

Now, add those three areas up and it makes that small office budget person lose control of their bowels and start munching bottles of Xanax.  So they start searching Google for “deploy apps to small office computers” or “patching small office computers cheap as hell” and things like that.

So, ConfigMgr already does the top 6 functions pretty darn well.  So what could be done to spin off a new sitcom version of this hit TV show for the younger generation?

  1. Simpler – It needs to be stupid-simple to install/deploy and manage.  This reaches into the UI as well.  Let’s face it, as much as I love the product, the console needs a makeover.  Simplify age-old cumbersome tasks like making queries and Collections, ADRs and so on.
  2. Lightweight – Less on-prem infrastructure requirements: DPs, MPs, SUPs, RPs, etc.  Move that into cloud roles if possible.
  3. Integrate/Refactor – Move anything which is mature (and I mean really mature) in Intune, out of ConfigMgr.  Get rid of Packages AND Applications, make a hybrid out of both.  Consider splitting some features off as premium add-ons or extensions, like Compliance Rules (or move that to Intune), OSD, Custom Reporting, Endpoint Protection, Metering, etc.
  4. Cheaper – Offer a per-node pricing model that scales down as well as up.  Users should be able to get onboard within the cost range of Office 365 models, or lower.

Basically, this sounds like Intune 3.0, which I’ve also blabbered about like some Kevin Kelly wanna-be futurist guy, but without the real ability to predict anything.

Some of the other responses on Twitter focused on ways to streamline the current “enterprise” realm, with things like automating many of the (currently) manual tasks involved with installation and initial configuration (SQL, AD, service accounts, IIS, WSUS, dependencies, etc. etc.), all of which are extremely valid points.  I’m still trying to focus on this “small shop” challenge though.

It’s really easy to stare at the ConfigMgr console and start extrapolating “what would the most basic features I could live with really come down to?” and end up picking the entire feature set in the end.  But pragmatically, it’s built to go 500 mph and slow down to push a baby stroller.  That’s a lot of range for a small shop to deal with, and they really shouldn’t.  That would be like complaining that the Gravedigger 4×4 monster truck makes for a terrible family vehicle, but it’s not supposed to be that.  And ConfigMgr really isn’t supposed to be the go-to solution for a group of 10-20 machines on a small budget.  Intune COULD be, but it’s still not there yet.  And even it is already wandering off the mud trail of simplicity.  It needs to be designed with a different mindset, but borrowing from the engine parts under the ConfigMgr hood.

Maybe, like how App-V was boiled down and strained into a bowl of Windows 10 component insertions for Office 365 enablement, and dayam that was a weird string of nouns and verbs, they could do something similar with a baked-in “device management client” in a future build of Windows 10.  Why not?  Why have to deploy anything?  They have the target product AND the management tool under the same umbrella (sort of, but I heard someone unnamed recently moved from the MDT world into the Windows 10 dev world, so I’m not that far off).

Does any of this make sense?  Let me know.

 

ConfigMgr Script Deployments

Introduction

The following caffeine-induced mess was the result of a quick demo session conducted with a customer about the use of the new “Scripts” feature in Configuration Manager 1710+.  There are other examples floating about the Internet which are equally good, if not better, but just finished unpacking, doing laundry, walking the dog, and needed something to do.

What is it?

The new “Scripts” feature allows you to perform “real-time” execution of PowerShell scripts against a Device Collection or individual members of a Device Collection.  It is worth noting that you cannot deploy to individual Devices from within the Devices node of the console, it only works from within, and beneath, the Device Collections node.  The script is executed on the client remotely, so the shell context is local to the remote client.  This means if you instruct the code to look at C:, it will be looking for C: on the remote device(s).

What Can You Use This For?

The answer to this question depends on your intentions and personality.  If you’re an eager workaholic, the sky is the limit.  If you’re a diabolical evil bastard with malicious thoughts, the sky is also the limit.  Is this potentially dangerous?  Yes.  But EVERYTHING in life is potentially dangerous, even brushing your teeth and going for a walk.  So weigh your risks and proceed accordingly.  I’ve provided a few examples below to illustrate some possible use cases.  Read the disclaimer before attempting to use any of them.

Preliminary Stuff

The first thing you need is to have Configuration Manager 1710 or later.  The second thing you need is to check the box to “Consent to use Pre-Release features” (Administration / Site Configuration / Sites / Hierarchy Settings / General tab).  The third thing you need (for testing anyway) is to un-check the box right below it that says “Do not allow script authors to approve their own scripts”.  If you do not un-check that option, you will be able to create script items, but you won’t be able to deploy them.

The next step is to enable the pre-release feature “Create and run scripts”:  Administration / Updates and Servicing / Features.  Right-click “Create and run scripts” and select “Turn on”. Once you’ve enabled the feature, the first time at least, you may need to close and re-open the console.  This is not always the case it seems, but I have seen this most of the time.

The Process

Create the Script

Once everything is enabled and ready to go, you should be ready to destroy, I mean, ready to begin.

  1. Select “Software Library”
  2. Select “Scripts”
  3. Select “Create Script” on the ribbon menu at top-left (or right-click and choose “Create Script”)
  4. Provide a Name
  5. Import or Paste the script code (only PowerShell is supported as of now)
  6. Tip: Make sure your script code returns an exit code of some sort to indicate success/fail to ConfigMgr (example: Write-Output 0)
  7. Click Next, Next, and Close

Approve the Script

  1. Right-click on the Script item
  2. Select Approve/Deny
  3. Click Next (I still don’t know why, but you have to, at least for now)
  4. Choose “Approve” or “Deny” and enter an “Approver comment”
    NOTE: Many organizations have procedures that require documenting approval authorization directly on the change items involved with a given change.  And to change that would require changing the way you manage change, which would require change management to effect the change and change the way you’re changing things.
  5. Click Next, Next and Close

Deploy the Script

  1. Select Assets and Compliance
  2. Select Device Collections
  3. Navigate to an appropriate device collection
    1. To deploy the script to all members, right-click on the Collection and select “Run Script”
    2. To deploy the script to individual members, select ‘Show Members’, right-click on each member (resource) and select “Run Script”
  4. Choose the approved Script from the library listbox, and click Next
  5. Click Next again (safety switch, good idea!)
  6. Watch the green bar thing slide across the progress banner a few times
  7. When it’s done, review the pretty Bar Chart.

    Select the “Bar Chart” drop-down to change reports to “Pie Chart” or “Data Table” display.
  8. Change the “Script Output” selection to “Script Exit Code” to view results by exit code values.

Parameters

You can include parameter inputs within a script by including the param(…) block at the very top.  As soon as you type in param ( and then enter a variable name, like $MyParam, you should notice the ‘Script Parameters’ node appear in the left-hand panel below “Script”.  Remember to close the parentheses on param ().  This adds a new set of options that you’ll see when you click Next in the Create Script form.

This allows you to make scripts more flexible at runtime, so you can provide specific inputs as needed, rather than making a bunch of duplicate scripts with only minor variations between them.

Examples

So, here are just a few basic examples for using this feature.  You can obviously apply more brain juice to this and concoct way-more amazing awesomeness than this stuff, but here’s a taste.  These are provided “as-is” without any warranty or guarantee of fitness or function for any purposes whatsoever.  The author assumes no liability or responsibility for use,  or derivative use, of any kind in any environment on any planet in any universe for any reason whatsoever, notwithstanding, hereinafter, forthwith, batteries not included, actual results may vary, void where prohibited or taxed, past results do not indicate future performance.

Collect Client Log Files

# Collect-ClientLogs.ps1
# Modify $TargetPath to suit your needs
$SourcePath = 'C:\Windows\CCM\Logs'
$TargetPath = '\\CM01.contoso.com\ClientLogs$\'+$($env:COMPUTERNAME)
if (!(Test-Path $TargetPath)) { mkdir $TargetPath }
robocopy $SourcePath $TargetPath *.log /R:2 /W:2 /XO /MT:16
if (Test-Path $TargetPath) {
  Write-Output 0
}
else {
  Write-Output -1
}

Refresh Group Policy

# Refresh-GroupPolicy.ps1
GPUPDATE /FORCE
Write-Output 0

Modify Folder Permissions

# Set-FolderPermissions.ps1
param (
  [parameter(Mandatory=$True)]
  [ValidateNotNullOrEmpty()]
  [string] $FolderPath
)
if (Test-Path $FolderPath) {
  ICACLS "$FolderPath" /grant 'USERS:(OI)(CI)(M)' /T /C /Q
  Write-Output 0
}
else {
  Write-Output -1
}

Summary

If you haven’t looked into this feature yet, I strongly recommend you give it a try IN A TESTING ENVIRONMENT.

How’s my driving?

Did I miss anything?  Did you find any bugs?  Let me know!

Thank you for reading!

Rants about Configuration Manager and PowerShell

Note: Although I’m still on hiatus, I was reminded about a few blog posts sitting in my drafts queue that need to get posted before they get stale like me.  There may be a few more.  Until then – cheers!

How many times have you seen PowerShell code that looks similar to the following?

param (
  [parameter(Mandatory = $True, HelpMessage = "Site Server Name", ValueFromPipeline = $True)]
  [ValidateNotNullOrEmpty()]
  [string] $ServerName, 
  [parameter(Mandatory = $True, HelpMessage = "Site Code")]
  [ValidateNotNullOrEmpty()]
  [string] $SiteCode
)
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" -Verbose:$False

Notice how this is expecting the user to (manually) provide the name of the server, the site code, and so on?  What bothers me most about this is that these two pieces of information are easy to obtain directly from the local computer.  Even when running on a workstation or member server which is not a ConfigMgr site system, you can easily obtain the site server name and the site code.  So why ask a user to key this in manually?

I’ve blogged about this general topic a few times before, but I still see most snippets posted online today doing this exact same approach to setting up the most basic, albeit “core” aspects, on which the rest of the script depends.  Bad idea!  So 1990’s.

Think of this in alternate contextual forms:

  • We expect to drop AD computers and users into Organizational Units (OUs) to automate Group Policy management processes.
  • We expect to drop ConfigMgr resources into Collections to automate policy and content deployment processes.
  • We expect to place AD domain controllers into Sites to automate replication optimization processes.
  • We expect to place AD users into security groups to automate permissions inheritance processes.
  • So why don’t we expect our scripts to drop into execution environments and inherit and automate processes as well?

Reiterating one of the lectures from my CS days in college, “every program needs to start with stated assumptions“.  Other great bits of advice: “If it can be automated, then automate the shit out of it.”, and, “If you automate a broken process, you can only get an automated broken process.” (that was from a manager, not a professor, but still top of my list).  I could go on much longer, but maybe that would be best for an in-person discussion or speaking event.  God help you.

So, the questions should be:  what then are the expectations when executing a program (or script)?  I realize this is 100-level stuff right here, but so-often I see people dive into writing code before they pause to answer some basic questions about how it will be used.  The most basic questions should be…

Where will it be used?

When will it be used?

How will it be used?

and… Who, will use it?

Let’s define some base assumptions:

  • Where?  On the ConfigMgr site server (locally or via PSRemoting, WSMAN/Rexec/WinRS/PsExec, etc.)
  • When?  On demand, or via scheduled job
  • How?  PowerShell + ConfigMgr Admin Console framework
  • Who?  A user or process-owner account which has local Administrator rights

Assuming this is going to be invoked on a Central Administration Server (CAS) or Primary Site Server (PSS), we can also assume that the ConfigMgr admin console will be installed.  And along with that, it will have the PowerShell cmdlet library available as well.

In this scenario, we can easily obtain the name of the server, as well as the Configuration Manager site code.  There’s no need to ask a user to manually input this information, because, as we’ve seen many times, manual intervention causes cars to crash, trains to derail, and space shuttles to explode.  Humans are bad.

There are several ways to get the site server name (locally):

# environment
$ServerName = ($env:COMPUTERNAME+'.'+$env:USERDNSDOMAIN)

# WMI
$ServerName = Get-WmiObject -Class Win32_ComputerSystem | Foreach {$_.Name+'.'+$_.Domain}

# registry
$ServerName = (Get-Item -Path HKLM:SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName).GetValue('ComputerName')

You get the idea.  The easiest (and fastest) may be the environment variable option.  So, we can also use this to set a default value even when using input parameters…

param (
  [parameter(Mandatory = $False, HelpMessage = "Site Server Name", ValueFromPipeline = $True)]
  [ValidateNotNullOrEmpty()]
  [string] $ServerName = $($env:COMPUTERNAME+'.'+$env:USERDNSDOMAIN)
)
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" -Verbose:$False

We can also fetch the ConfigMgr Site Code using the Registry…

$SiteCode = (Get-Item -Path HKLM:\SOFTWARE\Microsoft\SMS\Identification).GetValue('Site Code')
$OldLoc = (Get-Location).Path
Set-Location "$($SiteCode):\" -Verbose:$False
...

So, that works fine when the assumption is that the script will be invoked directly on a ConfigMgr site server.

As a side note, there’s plenty of other useful information exposed in the Registry location HKLM:SOFTWARE\Microsoft\SMS

Going Remote

Now, let’s change the assumptions a bit.  Now the script will be invoked on a workstation, which is joined to the same AD domain as the ConfigMgr site.  Assuming the workstation is also a ConfigMgr client, and the desired Site Server is also the Management Point (MP) for this workstation, we can fetch the server name from the local machine as well, but it resides under the client Registry tree, rather than the site system Registry tree…

$mp = ((Get-Item -Path HKLM:SOFTWARE\Microsoft\CCMSetup).GetValue('LastValidMP') -split '//')[1]

Even if the MP is not the Primary we wish to connect to, we can use the MP information and perform additional queries against its Registry to “walk-up” the hierarchy to find the Primary we wish to connect with (if necessary).

Note: The actual value of “LastValidMP” is stored in URI format… example…

"http://cm01.contoso.com"

So if we split the string into a 2-element array on the instance of double slashes, we can then grab the index=1 value (2nd element), which is the FQDN of the MP.  You could also use .Substring() or .Replace() to manipulate the string in order to remove the http:// prefix.

$mp = (Get-Item -Path HKLM:SOFTWARE\Microsoft\CCMSetup).GetValue('LastValidMP')
$mp = $mp.Substring(7)
# or
$mp = $mp.Replace('http://', '') # or -replace 'http://', ''

So, now we have the ConfigMgr site server (again, assuming this is a small site and the MP is the primary), we can still fetch the site code either from the local client environment, or from the remote registry.  Either will work…

$SiteCode = (Get-Item -Path HKLM:SOFTWARE\Microsoft\CCM\CcmEval).GetValue('LastSiteCode')

You may be thinking (or saying aloud) right about now “so what? what can I do with this from a workstation?”  Well, if the ConfigMgr Admin console is installed, you have access to the same PowerShell module that is available on the site server.  So, if you intend to run your script locally on a workstation (or member server) which has the ConfigMgr Admin console installed, you can automate the site server name, and site code parts of your script very easily…

$mp = ((Get-Item -Path HKLM:SOFTWARE\Microsoft\CCMSetup).GetValue('LastValidMP') -split '//')[1]
$SiteCode = (Get-Item -Path HKLM:SOFTWARE\Microsoft\CCM\CcmEval).GetValue('LastSiteCode')
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" -Verbose:$False
$OldLoc = (Get-Location).Path
Set-Location "$($SiteCode):\" -Verbose:$False

Keep in mind that no matter where you execute your script code, it will only be able to access resources which are allowed for the user context under which it is running.  So, if you run the script under your own domain account, and that account has limited rights in ConfigMgr, it’s going to be limited in what it can do against the site system environment as well.  I know many of you will shake your heads when reading this, but it is very often overlooked.

Speaking of the ConfigMgr Registry (SMS) tree

Beneath the HKLM:SOFTWARE\Microsoft\SMS registry tree, there are plenty of other useful pieces of information.

  • Server
  • Full Version
  • Domain
  • Parent Site Code (useful for Secondary sites and CAS environments)
  • Parent Server (ditto)
  • Site Name
  • Installation Directory
  • DatabaseMachineName
  • DatabaseName

So, even if your script needs to make some database connections to SQL Server (ADO, etc.) you can fetch the site database server and site database name, to automate the connection setup and continue onward.

Okay, so now what?  Well, there’s more.  Another thing I see way too-often is reinventing the wheel.  And not just any wheel, but octagonal wheels.  Here’s a few examples, and corresponding suggestions to avoid unnecessary work:

  • Writing elaborate ACL manipulation code, or invoking a blob of .NET reflection mess.
  • Writing elaborate code to manipulate local user permisions, like “logon as a service”
    • Use the Carbon PowerShell module – done (thank you Rob!)
  • Writing elaborate code to query or manipulate SQL Server settings
    • Use dbatools or SQLServer PowerShell modules
    • You can configure server memory and recovery model settings this way also (example)
  • Writing elaborate code to check if script is being executed via “run as administrator”
  • Manually writing help documentation
    • Enforce consistent commenting
    • Use PlatyPs to generate markdown files automatically (thank you Kevin!)
  • And finally…

Some final bits of advice:  NEVER test your scripts on a live Configuration Manager environment.  If you don’t have a test environment, build one, and test everything there BEFORE introducing into the production environment.  Also, when you are testing scripts against Configuration Manager and/or SQL Server, always keep the Task Manager window open and watch the Performance tab closely.

Summary

Am I some sort of “expert” when it comes to PowerShell or Configuration Manager?  Only when I’m around people who can’t spell “computer” and they’re serving alcohol.   I’ve just spent too many years soaking up what others have shared.  You are free to disregard everything I’ve said.  In fact, in America, it’s expected. But that’s okay too.

Random Thoughts and Stuff

While packing for travel I was having a conversation around the “state of IT” with a friend. So I figured (A) it might be worth jotting down some of the key points, and (B) do it before I fly out, in case some underpaid mechanic forgets to tighten that one bolt that holds the engine onto the wing.  Actually, who am I kidding, that mechanic probably earns more than I do.  Anyhow…

this is rambling, so drink plenty of medication and smoke your wine before continuing.

(travel-packing sidenote: Oreo is 15 years old, and like most humans, used to hate my guts.  Eventually, as daughter number 3 moved out, and I became her sole source of attention and food, she has become my friend.  She follows me around all day.  Every time she sees my suitcase out the night before I travel, she does this.  I’ll need to go over that ball cap with a ball of tape in the morning.)

The Future of SCCM and MDT and EMS

This is admittedly a Microsoft-centric topic. The discussion mentioned above was about “how long” will SCCM and MDT be “of interest” to consumers?  Obviously, I do not own a real crystal ball.  I only have a Dollar Tree knock-off, and I don’t have access to real business data, therefore I rely upon what scientists often refer to as ‘shit talking‘.

I’ll admit, after day 1 at MS Ignite this year, I was feeling the angst.  For example, while riding the bus back from the conference center to the hotel, staring out the window, I kept thinking “why, why, why did I leave app-dev to move into this infrastructure rat race?!  wtf was I thinking?!” and “I hope my dog isn’t chewing up my last pair of flip flops right now.”  It really felt like the job market for anyone walking around a typical IT shop today, would be dried up to around 10% of that volume within 5 years.  And who really knows?  It could go in any direction.  I think everyone is in agreement that there is already a major tectonic shift in play, with traditional operations moving into software-defined operations.  The thought of learning things like JSON, Git, VSTS, on top of PowerShell and Azure, is adding wrinkles to quite a few faces I see.

The mere mention of “new certs” is probably having a measurable effect on the sales of alcohol, tobacco and firearms, which should keep these guys employed for a long time.  For many I’ve known over the years, the feeling is like being shoved onto a rollercoaster by your drunk buddies, against your will, and now you’re approaching the top of the first peak in the run.

After 3 more days, and soaking up session content, food, beer, and more importantly: engaging vendors in deep discussions out on the expo floor, my initial expectations of SCCM/MDT doom were relaxed quite a bit.  Mainly out of realizing the following points:

  • The majority of imaging work I see being done at most mid-sized and large customers is refresh of existing hardware.  The mix of new vs. reuse is cyclical at most larger shops, due to budget cycles and SLA’s about hardware refresh programs.  So at various times in a given year, there’s more new imaging, but for the remainder of the year it seems to be more refresh/reuse.
  • Most of the (above) people I’ve spoken with are very interested in AutoPilot, but quite a few hadn’t yet been allowed access to the preview at the time I spoke with them (I think most are now seeing it in their portals)
  • In-place upgrades are still a minority (far too low in my opinion)
  • The description of “automatic redeployment” got their attention, but most are still tied to the comfort of a “clean wipe and load” for various reasons.
  • Ultimately: Regardless of what anyone says, things which “work” have a very VERY tough time dying in the IT world.  Hence why so many machines still run XP.  I’d also wager my tombstone that Windows 7 will easy to find running on machines in 2027.  That’s because I’m planning to be cremated on a Walmart grill.  But that’s beside the point.

The weeks after Ignite I’ve made it a point to casually interview my customers to get a feel for where they see the biggest and most immediate changes coming.  It’s a delicate thing to ask, since it can easily smell like a sales pitch.  Sales pitches have a distinct odor that is often confused with bus station toilets or dead cows laying in the sun.  However, most of them are well aware of my dislike for sales people in general (some of my friends are in sales, so there are exceptions).

  • The biggest hurdles they have today are keeping up with device models and drivers, patching, moving to some new system for IT operations (ticketing, change mgt, etc.), and endless training of each new-hire who is replacing three who just left.  See what I did there?
  • The single biggest complaint about imaging in general revolves around drivers.
  • There’s still quite a bit of frustration and confusion I hear around Intune capabilities.  Some is related to Intune agent vs. agentless, management; Some is around app deployment capabilities; Some is inventory reporting.

Windows 10

I still spend way too much time explaining Windows 10 servicing models and the naming convention.  They were just starting to grasp CB, and CBB, but now “Semi-Annual” and “Semi-Annual Targeted” are leaving them in one of two modes: pissed off or chuckling.  The most common response I hear from the former is around the constant renaming of things in general.  “Active, Live, Visual, and now CBB, then Semi Annual Targeted, WUS, then WSUS”, and so on.  Their words, not mine.

I’m always surprised to find so many shops still heavily invested in MDT and doing very well with it.  The other interesting thing is that the majority of them assume they’re doing everything wrong and are panicked when I arrive that I’ll redline their papers.  In fact, most of them are doing very well.  They’ve read the blogs, the tweets, bought the books, watched the videos, done the TechNet labs, and so on.  A few have been lucky enough to attend training and/or conferences as well, but that’s a very small percentage.

The pace at which organizations are getting their Windows 10 rollouts moving is gaining speed and volume.  However, the Office aspect has thrown a wrench into quite a few (see below)

Office

This reminded me of a discussion I had at Ignite with the Office team on the expo floor.  It was around the issue of third-party extensions (add-ons) for Office, and how many are produced by small shops which do not stay current with Office versions.  The result I’ve continued to see is a fair amount of shops who can’t upgrade Office until the third-party vendor puts out an update or a new version.  Then there’s the cost factor (is it free or not?).  In many of those cases, the hold-up triggered the IT department to wait on other projects such as Windows 10.

The number of Access applications interfering with Office upgrades has dropped significantly for me in the past 3 years.  Not just the projects I’m working on, but also from reports I get from other engineers and customers.  That’s a good thing.

Controls vs. Controls

I’m still seeing a continued reliance on inefficient control mechanisms with regards to device and user configuration management.  Way too much effort put into the imaging side, and not enough on the environment side.  Way too much on the “nice to have” side, vs. the “gotta have” side.  Not enough attention is being paid to ‘baseline’ and ‘continuous’ control models, and when to use which one, and how best to apply tools for each.  For example, hours and hours spent on wallpaper, shortcuts, and folders in the imaging process, being manually adjusted with each update cycle, rather than letting Group Policy Preferences step in and mop that shit up with one hand.

I’ve had fairly good results convincing customers to drop the continuous meddling with reference images, instead, (at least trying to)…

  • Change the data storage process to keep users from storing anything valuable on their device
  • Remove as many non-critical configuration controls as possible
  • Move continuous controls to the environment (GPO)
  • Move baseline controls to the environment wherever possible (GPP)
  • Move remaining baseline controls to task sequence steps

There are obviously exceptions that require mucking with the install.wim to make a new .wim, but I’m finding that’s only REALLY necessary in a small percentage of the time.  The vast majority of controls I see are voluntary and serve little functional or operational benefit.  Things like hiding Edge, Forcing IE, hiding Cortana, forcing Start Menu and Taskbar items, etc.  Just educate users to avoid them.  Treat them like adults and who knows, maybe they’ll stop urinating on your office chair.  Try it and see.  The worst you’ll get is whining (you get that anyway), but the best (and most likely) outcome is less work for you and less risk of something breaking.

Role and Salary Compression

It not only continues to thrive, it seems to be accelerating its pace.  Almost every customer I meet tells me how they’re expected to do more with fewer people, less training, less budget, and shorter time constraints.  Most haven’t had a significant raise in a long time.  This seems more prevalent at companies which are publicly-traded than those which are not, but it affects both.  Personally, I see a correlation with public organizations and cost reduction priorities over innovation and revenue increase.  Then again, I have no formal training in such matters, so again, I’m probably talking shit.  Again.

Intune

Speaking of Intune.  I’ve been spending more time with it this past week, along with Azure AD, and AzureADPreview powershell module.  I’ve always like the concept of what Intune and EMS are aimed at. The mechanics are still frustrating to me however.  There are plenty of design quirks that drive me batshit crazy.  Like devices / all devices vs. Azure AD devices, and the differences in how provisioning an app for iOS or Android and Windows, from the Windows Store no less.  As Ricky Bobby would say, “it’s mind-bottling”.

Then again, I’m not at all a fan of the “blade” UI paradigm.  The Blade model is (in my humble, semi-professional, almost coherent opinion) marginally efficient for direct touchscreen use, but for mouse and keyboard it blows chunks of post-Chipotle residuals.  I’m sure that will infuriate some of you.  But that’s just how I feel.  Drop-down menus, panels, heck, even ribbons, are more efficient in terms of hand and finger interaction (mouse, touchpad) for operations involving closely related tasks (settings, options, etc.)  Ask yourself if moving ConfigMgr to a blade UI would make it better?  Or Office?  If you think so, try switching to another brand of model glue.

Back to Intune.  I would really look forward to seeing it mature.  Especially in areas like agent vs. agentless device management (it’s very confusing right now, and the differences are weird), AutoPilot, Redeployment/Reset, and expanding the features for deploying applications, remote management (TeamViewer, etc.), and GPO-to-MDM migration.  I’m thinking Windows desktops and laptops of course (if you hadn’t already figured that out).  Phones are great, but nobody, who isn’t masochistic, is going to write a major app using their phone most of the time.  Auto-correct and latent-rich touch screen typing, would cause most PowerShell, C# or Ruby code writers to massage their head with a running chainsaw.

I think I digressed a bit.  sorry about that.

Other Stuff

I’ve spent some after-hours time keeping my brain occupied with scripting and app-dev projects.  I’ve been doing Windows 10, Windows Server 2016, MDT and SCCM lab builds and demos for months, along with real implementation projects, and starting to burn out on it all.  I needed a break, so I’ve managed to get a few things done and few in the pipeline:

I ran across a couple of (seem to be) abandoned Git projects focused on DSC for building ConfigMgr sites, but none of them appear to be factored into a template construct.  Meaning?  That they’re still built on specific parameters, or require extensive customization for various roles, configurations, environments.  I’m still poking at them and forked one to see what I can make it do.  In the meantime, I’m moving ahead with CM_BUILD and CM_SITECONFIG being merged into a new CMBuild PowerShell module.  So far so good.  And when that’s done, I’ll go back to see what I can in that regard with DSC and applying that towards Azure VM extensions.

I’ve come to realize that there’s 4 basic types of southern dialect in America:  Fast, Slow, Twangy and Crooked Jaw.  Think about it, and you’ll see that’s true.

The shortest distance between two points is often the most expensive.

If you never fail, you’re not trying hard enough.

If you fail most of the time, you’re probably in the wrong career path.

I’m on travel next week.  Expect more of the same stupid tweets about mundane stuff.  If you tire of me, unfollow me.  I don’t mind.  It’s just Twitter after all.  I will do my best to keep my camera ready for anything interesting.  I need to watch some air crash documentaries now, to get my mind relaxed for tomorrow.

CMHealthCheck is now a PowerShell Module

What is it

CMHealthCheck is a PowerShell module filled with bubble wrap, Styrofoam peanuts, and 2 possibly useful functions, aimed at collecting a bunch of data from a System Center Configuration Manager site server, and making a pretty report using Microsoft Word.  But doing so without needing to manually download and store a bunch of script files and so on.

It’s still based on the foundations laid by Rafael Perez, with quite a bit of modification, prognostication, prestidigitation, and some coffee.  Special thanks to Kevin (@thenextdotnet) for helping point me in the right direction to move it all from scripts into a module.

Why is it

I get asked (okay, told) to help customers find out why their site servers are running slow, showing red or yellow stuff, or just to get them ready to upgrade from whatever they’re on to whatever is the latest and greatest version of ConfigMgr.  They also like a pretty Word document with a spiffy cover page.

How to use it

  1. Install the module on the ConfigMgr CAS or Primary server you wish to audit –> Import-Module CMHealthCheck
  2. Run the Get-CMHealthCheck function (see documentation on Github – linked below)
  3. Install the module on a Windows computer which has Office 2013 or 2016 installed (hopefully NOT the same computer which was audited)
  4. Run the Export-CMHealthCheck function (see documentation on Github – linked below)
  5. Save the Word document and mark it up.

Where to Get it

How to Complain About it

Because I know some of you will, but that’s okay.  Without complaints, we have no way of identifying targets.  Just kidding.  I need feedback and suggestions (or winning lottery numbers and free food coupons).  Please use the “issues” link on the GitHub repository to submit your thoughts, gripes and so on.

Invoke CM_Build over the Web

Updated 10/15/2017 – Added -Override example

118057481

First off: WTF is CM_BUILD?

CM_BUILD is a PowerShell script that configures a “vanilla” Windows Server machine into having Configuration Manager Current Branch installed.  This includes ADK, MDT, Server Roles and Features (WSUS, BITS, etc.), SQL Server, ConfigMgr itself, and a few goodies like Right-Click Tools, ConfigMgr Toolkit.  The GitHub repo has a cute readme markdown page filled with overcaffeinated gibberish on how to use it.  CM_SiteConfig is the “part 2” to cm_build, which configures ConfigMgr into a semi-functional site.

Short answer: https://github.com/Skatterbrainz/CM_Build

Okay, why CM_BUILD?

I don’t know.  Why do we do anything?  For the thrills? I could have taken up robbing banks, raising a crocodile farm, or breaking world records of swilling down cans of Four Loco while working on electrical equipment.  But I chose the boring life.  And while I’m bored, I hate clicking buttons repeatedly, so …

I got inspired by Johan and Mikael’s ConfigMgr Hydration Kits and Deployment Fundamentals Vol. 6 book examples, and Niall’s noob scripts, (I know it’s not actually called that, but it sounds cool to say “Niall’s noob scripts“), and after 45 cups of terrible coffee I said “I can shove all that into an XML file and call my JSON friends up and laugh hysterically at them, saying things like ‘You and your snotty little JSON drivel!  Always mocking poor, starving little XML.  Well, I’ll have you know I can still write XML, and probably even a little COBOL! So what do you think of that?!  Hello?  Hello?  Did you just hang up on me?!! WTF!

Anyhow…. Hold on, I need to get my dog outside before she has an accident….

okay, I’m back.

Why Invoke it over the Web?

There are several potential reasons for wanting to do this:

  • I was really bored and it’s been raining all freakin day, and…
  • It’s 3am and I can’t sleep, and…
  • I saw this, and …
  • I wanted to pull this off within Azure, using a VM extension, without having to import any actual files, and it would be cool to tie all this together with a runbook so I can send a text message “new lab configmgr p01“, to fire off a lab build in Azure and have it text me back “your stupid lab is ready, now leave me alone!” then I can forget it’s still running and it runs all my MSDN credits down to $0 until the next monthly cycle, and…
  • I scrolled through Dan Bilzerian’s twitter feed just long enough to hate my boring life, and needed a distraction, and…
  • It seemed like something cool to try

Example

Time to put on a poker face and act serious now.  The example below calls the cm_build.ps1 script from the GitHub master branch URL, converts it into a -ScriptBlock object, and passes in the -XmlFile parameter value using the Github Gist raw URL (you can make your own by copying the cm_build.xml into your own “secret” Gist, so you don’t openly share sensitive information to the whole world)

$ps1file = 'https://raw.githubusercontent.com/Skatterbrainz/CM_Build/master/cm_build.ps1'
$xmlfile = '<your-gist-raw-url>'

$script = Invoke-WebRequest $ps1file
$scriptBlock = [ScriptBlock]::Create($script.Content)
Invoke-Command -ScriptBlock $scriptBlock -ArgumentList @($xmlfile, $True, $True, $True)

But you can also invoke the interactive gridview menu using the -Override parameter, by simply appending one more $True into the -ArgumentList array.

Invoke-Command -ScriptBlock $scriptBlock -ArgumentList @($xmlfile, $True, $True, $True)

Then you get this budget-sized, corner-cutting, hack of a menu to choose from…override-gui

You may see a red warning about “Split-Path : Cannot bind argument to parameter ‘Path’ because it is null.”  I’ll fix that soon.  It only impacts the log output, but nobody reads log files anyway, right?

Anyhow, it’s 3:33 am, and I’m still typing, which is probably bad for my health, but if two people read this and it actually provide useful information one of you, mission accomplished.  Actually, I know for a fact this is bad for my health.  Regardless, I ran the above snippet (with a real URL in the $xmlfile assignment) in my Hyper-V duct-tape and chewing gum lab at home, and it worked like a charm.  Now I can log into the server, open the ConfigMgr console and proceed with CM_SiteConfig, or apply real world tactics and break the ConfigMgr site entirely and start over.

zzzz