Devices, Scripting, Technology, windows

100-level 101

By my semi-quasi scientific reasoning, I estimate that this scenario has occurred in my presence approximately 34.75 times in the past 10 years. That number could be completely fictitious, but you have to prove me wrong, so good luck.

Anyhow, it happened yesterday, and today I had to actually apply it again myself, so it reminded me to blabber about it again, here, on my blabber blog. Remember, this is 100-level 101 stuff, so if you start rolling your eyes, I warned you already.

Challenge: You need to confirm a registry key is set on a remote client, RIGHT THIS FREAKING SECOND. The registry key is under one of the users who uses that machine. You only know the following:

  • The machine name
  • The user’s first and last name

Caveats: You are logged onto one of the domain controllers. You do not have Configuration Manager. You only have a keyboard, a mouse, a brain, a pair of eyeballs, and possibly a sleeping dog and angry cat nearby. Nothing else. Clothing is optional.

Workflow:

  • You ping the remote computer (e.g. “DT001”) and it responds with a happy wave and a smile.
  • You open trusty, old, bearded REGEDIT.exe and click File / Connect Network Registry. You enter the computer name (e.g. “DT001”). It tells you to **** off.
  • You apply some wax to your mustache and curl the ends neatly, crack your knuckles and continue. If you don’t have a mustache, use someone else’s for now.
  • Open a PowerShell console
  • Type: Get-Service RemoteRegistry -ComputerName DT001
  • It returns some information, including Status = “Stopped”
  • You attempt to start it: Get-Service RemoteRegistry -ComputerName DT001 | Start-Service. But it tells you to **** off.
  • You crack your knuckles once more and dawn a sinister look, like Daniel Day Lewis in There Will Be Blood
  • Set-Service RemoteRegistry -ComputerName DT001 -StartupType Manual
  • Get-Service RemoteRegistry -ComputerName DT001 | Start-Service
  • So far, so good. Go back to REGEDIT and connect successfully
  • You open HKEY_USERS and see a bunch of SID stuff, like “S-1-5-21-1234567890-0987654321-234234234234-1234, but you don’t know which one is related to the desired user account
  • Your dog reminds you that you are currently logged onto a domain controller.
  • You know the user is “Jimmy Jerkweed”, so you search for him using Get-ADUser -Filter ‘Name -like “*Jerkweed*”‘ | select *
  • You find one with a SID property that matches the registry key names and dive in

The Short Version

  • ping DT001
  • Set-Service RemoteRegistry -ComputerName DT001 -StartupType Manual
  • Get-Service RemoteRegistry -ComputerName DT001 | Start-Service
  • Regedit.exe / Connect Network Registry / DT001
  • Get-ADUser -Filter ‘Name -like “*jerkweed*”‘ | select SID

Way too many times, this would stop at the second bullet (above). The technician would insist that either a firewall, or anti-virus, were blocking access. Or maybe there was a problem with the machine. Not so.

  • By default, the Remote Registry service is disabled. Therefore, it cannot be forced to start, especially remotely.
  • Without this service running, you cannot connect to the registry from a different machine on the network, regardless of your privileges.
  • In most cases, by default, as a user with direct (or indirect) administrative rights on the remote machine, you can change the service startup type property from “disabled” to “manual”, allowing you to then start it, even remotely.
  • When using a Windows workstation, or member server (not a domain controller), you can also run the Get-ADxxxx cmdlets, if you have RSAT installed and enabled. If you don’t, and can’t, you can install the AdsiPS powershell module and do the same using Get-AdsiUser.

Cheers!

Advertisements
System Center, Technology

Support Requests – 2017 Flashback

I was just talking with someone about how “times have changed” just since 2017. Then I found an old email which had a list of cases I was working on around Q1-17 (former employer). Compared to then, 2019 has been much more calm.

  1. ConfigMgr client push account not having permissions on the remote devices.
  2. Over-zealous Antivirus settings getting in the way (McAfee) of ConfigMgr client installations.
  3. Network admins added/changed subnets without telling SCCM admins (site boundary updates)
  4. Using separate accounts in trusted AD forests, rather than a central trusted account, and the passwords were out of sync.
  5. Another team installed a 3rd party help desk product on the SQL host as SCCM uses, and didn’t tell them it hogs most of the available memory and violates the terms of the ConfigMgr/SQL license.
  6. After suggesting the use of an isolated IP subnet and dedicated DP for a central imaging workbench, the server admin team instead added a 2nd NIC to both the SCCM primary site server and a different DP, on different subnets, one without a gateway, and didn’t tell the SCCM anyone else.
  7. IT staff enrolled several Surface Book’s with EMS/Intune, and then removed the Intune client and installed the SCCM client and then opened a support ticket about why the client no longer shows as “managed” in Intune.  Microsoft investigated, explained and closed the request (as they should have).  The customer argued to keep the request open.  I was brought in to help explain why it should be closed (that alone took 2 days).
  8. Primary site server has CrowdStrike, Symantec EP, and Malware Bytes agents installed, all are active, and none have ConfigMgr exclusions. Long day.
  9. Client Push installation has custom settings which set the default MP to one that was removed from the environment years ago.
  10. Network team re-assigned subnets during an office relocation.  No one was notified to update AD sites and subnets or ConfigMgr (site boundaries).
  11. DNS scavenging was turned off, with DHCP lease duration of 3 days and 50% of devices roam around the campus every day or two.

Projects, Scripting, System Center, windows

sktools

sktools2

UPDATE: 1/14/2019 – version 1901.13.2 was posted to address a problem with the previous upload.  Apparently, I posted an out-of-date build initially, so I’ll call this the “had another cup of coffee build”.

Dove-tailing from the previous idiotic blog post, I’ve taken some time off to retool, rethink, redesign and regurgitate “skattertools” as a single PowerShell module.  The new version blends PoSHServer into the module and removes the need to perform a separate install for the local web listener.  The first version of this is 1901.13.1 (as in 2019, 01=January 13th, 1st release).

How to Install and Configure sktools

  • Open a PowerShell console using Run as Administrator
  • Type: Install-Module sktools
  • Type: Import-Module sktools
  • Type: Install-SkatterTools (this creates a default “sktools.txt” configuration file in your “Documents” folder)
  • Type: Start-SkatterTools
  • Open your browser and navigate to http://localhost:8080

This next part is only temporary, and will be improved upon soon:

  • Once the web console is open, expand “Support” and click “Settings” and modify to suit your Configuration Manager site environment.
  • Close and reopen the PowerShell console (still “Run as Administrator”)
  • Type: Start-SkatterTools
  • Refresh your web browser session

Work will continue until morale is eliminated.  Easter eggs are included, sort of.  Thoughts, feedback, bug reports, enhancement requests, angry snarky comments, are all welcome.  Enjoy!

Personal, Projects, Scripting, System Center, windows

And now for another stupid pet project

First, there was project number one. I called it “WWA”, which was a clever short name for “Windows Web Admin”. Even though I kept hearing it sounded like a name for a wrestling tournament.  Anyhow, it fell over and sank into a swamp.

Then there was project number two, or “AppAdmin”, which almost fell over and sank, but it was built inside a big shipyard, and they don’t let things sink there, so it floated for a while (I’m told it’s still afloat somehow).

Then, there was project number three, but I can’t state it’s name for legal reasons, or because I promised it might result in me delivering a flaming box of dog poo to a certain someone’s porch, after they ruined that project just as it was maturing, but that’s for another time and place.

Then there was project four, but I can’t talk about that one either, so I’ll skip to project five, CMWT.  But nobody cares about that one, so number six, was putting a hand-rubbed wax finish on someone else’s PowerShell script, and tossing it up on GitHub and PowerShell Gallery, along with projects seven, eight and nine.  And I’m surprised I still remember how to spell the number 8.  So anyhow…

Announcing SkatterTools

(imagine Morgan Freeman narrating from here on)

What is it?

Skatterbrainz Tools.  A really clever name.

It’s a portable web console app thing, for viewing and modifying things in your Active Directory and Configuration Manager environments, from the comfort of your beer-stained laptop.  Think of it like CMWT if it were (A) trying to copy the concept from Microsoft Windows Admin Center, and (B) didn’t require using a separate “server” or anything special**.  Yes, those are double-asterisks.  That means there’s some hidden footnote down below, but don’t look yet, I have to finish boring the shit out of you with this part first.

Why is it?

Because I needed a break from other things, like family matters during the holidays, a dog that loves chewing on furniture, and a 20 year old cat that wanders the house at 3am making really weird sounds.  And I just wanted to see if it was possible to…

  • Build a 100% web console UX to interface with AD and ConfigMgr using PowerShell
  • Not have to touch IIS or any web hosting mess
  • Make it customize-able, free, and open-source
  • Make it through the holidays once again

Where is it?

  • Like a lot of my stuff, it’s up on GitHub

What can it do?

  • View and cross-link:
    • AD users, computers, groups, sites, sitelinks, domain controllers, OUs
    • ConfigMgr users, devices, collections, applications, packages, boot images, task sequences, updates, and scripts
    • ConfigMgr site status, queries, discovery methods, certificates, Forest publishing, boundary groups and boundaries
    • Software inventory, software files
  • Manage:
    • Add/remove AD group members
    • View computers by AD user profile paths
    • Add/remove ConfigMgr collection members **
    • Those damned double-asterisks again, hmm.

Installation and Setup

  • Download PoSH Server here and install it (don’t worry, I checked it and it seems safe, you can trust me, I worked for the government once, sort of)
  • Download the GitHub repo (big green button – top right – zip option)
  • Extract the “poshserver” folder from the GitHub download into a local path like C:\ThisIsTheDumbestShitEver
  • Open the “config.txt” file and edit the settings to suit your needs
  • It is now ready to blow your mind, almost

Starting it Up

  • Add some gasoline and finely-crushed road flares, oh, wait, wrong stuff…
  • Create a desktop shortcut named “Start SkatterTools”…
Target: powershell.exe Start-PoshServer -HomeDirectory "c:\ThisIsTheDumbestShitEver" -CustomConfig "c:\ThisIsTheDumbestShitEver\sktools.ps1"
  • Create another desktop shortcut, named “Open SkatterTools” or “Coolest Shit Ever!”…
Target: http://localhost:8080/
  • Right-click the first shortcut, select “Run as Administrator”, and wait for it to open and say something like this…
  • Double-click the second shortcut and have your Kleenex box nearby
  • Click on one of the sidebar headings and watch the slick CSS stylings ooze all over your eyeballs and onto the floor.  Compliments of some sample code I found on W3Schools.  What a great site.

If you need to shut it down, just close the browser and close the PowerShell console.  There’s instructions on the PoSH Server site for how to configure it like a service, so it runs as a background job. You don’t have to do that though.

Is there any official support?

  • Are you kidding?
  • You can submit bug reports and enhancement requests using the “Issues” link on the GitHub repo.
  • Work comes first.  I have to keep my customers happy and my bills paid
  • I’m still adding things to it frequently, but work may cause some delays getting around to it
  • You can submit your own changes via GitHub (pull requests, etc.) or just submit Issues if you prefer

Is there a roadmap?  Where is it going next?

  • Real (stupid) men don’t use maps!  The journey is the dream, man.
  • Where are any of us really going?  You ever ask that question?
  • Don’t ask that question, it’s depressing.  Enjoy the now.
  • Seriously, yes, I have a metric butt-ton of things I plan to add or improve

Double Asterisk-o-rama

  • Double-asterisks denote two things here:
    • Features are not yet complete.  Things will change.  Oceans dry up. Mountains wear down. Regimes are toppled.  Keith Richards is forever.
    • This is free stuff, and it comes without any strings attached.  No warranties, or guarantees.  No promises (other than it might possibly entertain you if you’re bored), and poor you gets to assume any and all liability, risk and responsibility for anything bad if you use it improperly or in a production environment of some kind, or any environment where alleged (love that word) damages may have occurred or been coerced by tertiary incidental hereinafters forthwith and notwithstanding, that are void where prohibited, taxed or regulated. Batteries not included.
    • Whatever that means

Cheers!

Scripting, System Center, Technology

A Windows 10 Imaging 2-Step Boogaloo

It’s a new dance, and it goes like this…

Step 1 – Left foot forward: Image the device with a generic name, unplug, place on a shelf

Step 2 – Right foot to the side: Fetch from shelf, run script to assign to a user, hand device to user, go back to surfing Twitter

What could possibly go wrong?

Caveat Stuff

This “procedure”, if you will, is predicated on a scenario where the devices are NOT going to retain the auto-generated name when going into production.  They will instead use a unique name based on whomever they are assigned to (e.g. SAMaccountName, etc.).  If you can, I strongly recommend NOT doing this, which would seem strange that I’m essentially negating all of the remainder of this stupid blog post and telling you to just follow step 1, sort of.  However, if you insist on using “JSMITH”, or some other ad hoc data entry value, for the device name, then by all means, drink up, snort up, shoot up, and continue reading.  Thank you!

Errata / Disclaimer / Legal Stuff

At no point in any time in inter-galactic history, for any purpose or interstellar war or planetary conflict, shall anything mentioned herein be provided with any semblance of a warranty, guarantee, or promise that it will be error-free or suitable for your needs.  Nor shall this brainless author assume any liability, or responsibility for any direct, indirect, or alleged damages or loss of productivity, possibly attributed to the direct or indirect use of any information provided herein, for any purpose, explicit or implied, notwithstanding hereinafter for any jurisdiction of human societal or governmental law, or any group of suits on a golf course, related therein.  Golf carts and Martinis are not included.

…and One More Thing

Many blog posts / articles tend to portray a tone of “this is how it’s done”.  This blog post is different for two reason: (a) It’s just ONE example of dealing with ONE common scenario, out of quadrillions of bazillions and kadrillions of possible scenarios, and (b) it’s likely to be the dumbest article you’ve read today.

Step 1 – Image and Stage Device

This step is all about imaging a new device (or wipe/reload an existing device) whereby it isn’t immediately assigned to some whiney complainer, oops, I mean user.  It goes on a shelf, gathering dust, while it awaits being assigned to someone.

  1. Create / Copy / Hallucinate a PowerShell script:
    > It derives a name using available data (ex. Serial number, MAC, etc.).
    > Save the script in a shared location to allow for making a Configuration Manager Package.
    > Refer to horrifically inept script example further below.
  2. Create a new Package in Configuration Manager
    > Note: if you already have a OSD-related package for bundling your script goodies, just toss it in with the rest and they’ll play like over-caffeinated kids in one of those gooey McDonald’s Playland ball pits.
    > Distribute or Update Distribution on the Package
  3. Add a step to your OSD Task Sequence
    > Insert just before “Apply Operating System”
    > Run PowerShell Script –> Choose the Package, and enter the script name and parameters/arguments, select “ByPass”
    > Note: If you want to assign a common OU just assign it in the Task Sequence “Apply Network Settings” step, or add your own “Join Domain or Workgroup” step.
  4. Deploy the Task Sequence
    > If you target “All Unknown Computers”, make sure the collection does not have the “OSDComputerName” Collection Variable attached

Step 2 – Provision and Assign to Hapless User

This step is all about getting up from your desk, grunting and complaining the entire way, maybe knocking over your cup of cold coffee, to shuffle slowly over to the dust-covered shelf, fetching a pre-imaged device, and doing some doodling on it so it can be handed to a bitchy customer, oops, again, I mean user.  Okay, in all seriousness, you may be lucky today, and the user is actually a cool person.  But you’re reading my blog, which means you’re probably not that lucky.

  1. Plug device into your network
  2. Find something to talk about while you wait for it to boot up
  3. Log in using your magical omniscient IT wizard power account
  4. Run a crappy half-baked PowerShell script which renames the device and moves it to a special AD Organizational Unit (OU) to suit the user’s department, etc.
  5. Wait for the reboot
  6. Unplug the device
  7. Throw at the user as hard as you can
  8. Go back to reading Facebook and Twitter
  9. Wait for Security to arrive and escort you out of the building

Horrifically Inept Script Examples

I told you they were going to be horrific and inept, but you didn’t think I was serious.

Script 1 – Assign a “Temporary” Device Name during OSD Task Sequence

Save this mess to a file named “Set-DeviceName.ps1”

[CmdletBinding()]
param (
  [parameter(Mandatory=$False)]
  [ValidateNotNullOrEmpty()]
  [string] $Prefix = "TMP"
)
$SerialNum = Get-WmiObject -Class Win32_SystemEnclosure | Select-Object -ExpandProperty SerialNumber
$NewName = "$Prefix-$SerialNum"
# in case you're imaging a VM with a stupid-long serial number...
if ($NewName.Length -gt 15) {
  $SerialNum = $SerialNum.Substring(0,15-($Prefix.Length+1))
}
$NewName = "$Prefix-$SerialNum"
try {
  Write-Verbose "new device name = $NewName"
  $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
  $tsenv.Value("OSDComputerName") = $NewName
  Write-Verbose "OSDComputerName = $NewName"
}
catch {
  Write-Verbose "not running in a task sequence environment"
  Write-Host "new device name = $NewName"
}

Script 2 – Provision Device for Assigned User

Note: The following chunk of PowerShell code might look impressive, but that’s because I didn’t create all of it.  I just modified original examples shared by John Warnken and Stephen Owen.  Save this mess to a file named “Assign-UserDevice.ps1”.  This script relies on the “Locations.csv” file to provide the list of locations and department codes for the popup form.

[CmdletBinding()]
param (
  [parameter(Mandatory=$False, HelpMessage="CSV input file path")]
    [string] $CsvFile = "",
  [parameter(Mandatory=$False, HelpMessage="Form Title")]
    [ValidateNotNullOrEmpty()]
    [string] $FormTitle = "Contoso - Provision Device",
  [parameter(Mandatory=$False, HelpMessage="Maximum UserName character length")]
    [ValidateRange(1,15)]
    [int] $MaxUserNameLength = 11,
  [parameter(Mandatory=$False, HelpMessage="Force Upper Case username")]
    [switch] $IgnoreCase,
  [parameter(Mandatory=$False, HelpMessage="Keep existing OU location")]
    [switch] $KeepOuLocation,
  [parameter(Mandatory=$False, HelpMessage="Apply Changes")]
    [switch] $Apply,
  [parameter(Mandatory=$False, HelpMessage="Do not force a restart")]
    [switch] $NoRestart
)

$ScriptPath = Split-Path -Parent $PSCommandPath
if ($CsvFile -eq "") {
  $CsvFile = Join-Path -Path $ScriptPath -ChildPath "Locations.csv"
}

function Move-ComputerOU {
  param (
    [parameter(Mandatory=$True)]
    [ValidateNotNullOrEmpty()]
    [string] $TargetOU
  )
  $ComputerName = $env:COMPUTERNAME
  $ads=[adsi]''
  $adssearch = New-Object DirectoryServices.DirectorySearcher
  $adssearch.searchroot = $ads
  $adssearch.filter="(objectclass=computer)"
  $adc1 = $adssearch.findall() | Where-Object {$_.properties.item("cn") -like $ComputerName}
  $ComputerDN = $adc1.properties.item("distinguishedname")
  Write-Verbose "distinguishedName = $ComputerDN"
  $adc = [adsi]"LDAP://$ComputerDN"
  $targetOU="LDAP://$targetOU"
  Write-Verbose "target path = $targetOU"
  $adc.psbase.MoveTo($targetOU)
}

if ($MaxUserNameLength -gt 9) {
  Write-Warning "UserName portion cannot be longer than 9 characters when the prefix is 6 characters long"
  break
}

if (!(Test-Path $CsvFile)) {
  Write-Warning "CSV Input file not found: $CsvFile"
  break
}
$LocData = Import-Csv -Path $CsvFile

[xml]$XAML = @' 
<Window 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  Title="" 
  Height="200" Width="320" Topmost="True" WindowStyle="ToolWindow" 
  WindowStartupLocation="Manual" Top="200" Left="200" 
  FocusManager.FocusedElement="{Binding ElementName=ComputerName_text}"> 
  <Grid> 
    <Label Name="Label_Warn" Content="" HorizontalAlignment="Left" Foreground="#ff0000" Height="27" Margin="15,0,0,0" VerticalAlignment="Top" Width="300" />
    <Label Name="Label_Loc" Content="Loc+Dept" Foreground="#000000" HorizontalAlignment="Left" Height="27" Margin="15,20,0,0" VerticalAlignment="Top" /> 
    <Label Name="Label_Dlm" Content="-" Foreground="#000000" HorizontalAlignment="Left" Height="27" Margin="125,50,0,0" VerticalAlignment="Top" />
    <Label Name="Label_Num" Content="UserName" Foreground="#000000" HorizontalAlignment="Left" Height="27" Margin="150,20,0,0" VerticalAlignment="Top" />
    <ComboBox Name="Combo_Loc" Margin="20,50,0,0" Height="27" Width="90" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Center">
    </ComboBox>
    <TextBox Name="Text_User" Margin="150,50,0,0" Height="27" Width="90" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Center" Text="" MaxLength="20" CharacterCasing="Lower" />
    <Button Name="Button_Continue" Content="Continue" Margin="90,100,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Height="27" Width="100"/> 
  </Grid>
</Window> 
'@
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 

# Read XAML string and convert into a form object
$reader = (New-Object System.Xml.XmlNodeReader $xaml) 
$Form = [Windows.Markup.XamlReader]::Load( $reader ) 

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

foreach ($loc in $LocData) {
  $LocDept = "$($loc.Loc)$($loc.Dept)"
  $Combo_Loc.AddChild($LocDept)
}

$Form.Title = $FormTitle
$Text_User.Maxlength = $MaxUserNameLength
if (!($IgnoreCase)) {
  $Text_User.CharacterCasing = "Upper"
}
# add form handler for pressing Enter on UserName text box
$Text_User.add_KeyDown({
  if ($args[1].key -eq 'Return') {
    Write-Verbose "action -> user pressed Enter on username textbox"
    $Location = $Combo_Loc.SelectedValue
    $UserName = $Text_User.Text.ToString()
    Write-Verbose "selection -> $Location"
    Write-Verbose "username -> $UserName"
    if (!([string]::IsNullOrEmpty($Location))) {
      $Script:LocIndex = $Combo_Loc.SelectedIndex
      $Script:NewName = $Location+'-'+$UserName
      $Script:Ready = $True
    }
    $Form.Close() 
  }
})
# add form handler for clicking Continue button on exit
$Button_Continue.add_Click({
  Write-Verbose "action -> pressed Continue button"
  $Location = $Combo_Loc.SelectedValue
  $UserName = $Text_User.Text.ToString()
  Write-Verbose "selection -> $Location"
  Write-Verbose "username -> $UserName"
  if (!([string]::IsNullOrEmpty($Location))) {
    $Script:LocIndex = $Combo_Loc.SelectedIndex
    $Script:NewName = $Location+'-'+$UserName
    $Script:Ready = $True
  }
  $Form.Close() 
})
# display the form for the user to interact with

$Form.ShowDialog() | Out-Null

if (!($Script:Ready)) {
  Write-Warning "No selection or entry. Nothing to do."
  break
}

$RowSet = $LocData[$Script:LocIndex]
$OuPath = $RowSet.DeviceOU

if ($Apply) {
  Write-Host "New Name...: $NewName" -ForegroundColor Green
  if (-not ($KeepOuLocation)) {
    Write-Host "OU Path....: $OuPath" -ForegroundColor Green
    Move-ComputerOU -TargetOU $OuPath
  }
  Write-Verbose "renaming computer to $NewName"
  Rename-Computer -NewName $NewName -Force
  if (!($NoRestart)) {
    Restart-Computer -Force
  }
}
else {
  Write-Host "Test Mode (No changes were applied)" -ForegroundColor Cyan
  Write-Host "New Name...: $NewName" -ForegroundColor Cyan
  if (-not ($KeepOuLocation)) {
    Write-Host "OU Path....: $OuPath" -ForegroundColor Cyan
  }
}

Locations.csv File for Assign-UserDevice.ps1

Note: “Loc” can be a building, campus, city, or whatever.  The ADGroup column is for future/optional/possible/potential use for adding the computer to an AD security group as well.

Loc,Dept,DeviceOU,ADGroup
BOS,HR,"OU=Workstations,OU=HR,OU=Boston,DC=Contoso,DC=local",
BOS,RD,"OU=Workstations,OU=Research,OU=Boston,DC=Contoso,DC=local",
MIA,HR,"OU=Workstations,OU=HR,OU=Miami,DC=Contoso,DC=local",
MIA,MK,"OU=Workstations,OU=Marketing,OU=Miami,DC=Contoso,DC=local",
SFO,FN,"OU=Workstations,OU=Finance,OU=SanFrancisco,DC=Contoso,DC=local",
SFO,HR,"OU=Workstations,OU=HR,OU=SanFrancisco,DC=Contoso,DC=local",
SFO,RD,"OU=Workstations,OU=Research,OU=SanFrancisco,DC=Contoso,DC=local",
TMP,HR,"OU=Workstations,OU=HR,OU=Tampa,DC=Contoso,DC=local",

Cheesy Examples

Example: Assign-UserDevice.ps1 -MaxUserNameLength 9 -Verbose

Summary and Conclusion

As you may have surmised by now, everything you’ve read above is completely stupid and useless. You’re shaking your head in disbelief that you skipped some other opportunity to read this, and you should have chosen otherwise, even if that other opportunity was a prostate exam.  You are now dumber for having read this.

You’re welcome.

Devices, Scripting, System Center, Technology, windows

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!

Technology

Windows 10 Soup Sandwiches

Version 2.1 (I lost Version 1.0 somehow, from the old blog, but it was focused on Windows 7, 8.1 anyway)

An ad hoc collection of wine-infused recipes to help smoosh Windows 10 like a ball of clay, or like a soggy sandwich.

Disable Windows Firewall (MDT, SCCM)

Disable Windows Firewall (GPO)

Disable Windows Defender (GPO)

Deploy .NET Framework 3.5 with Feature on Demand (GPO)

Enable Controlled Folder Access (GPO)

Create Shortcuts on Desktop, Start Menu (GPO)

Disable IPv6 (GPO)

Configure, Start, Stop Windows Services (GPO)

Block and Disable Cortana (GPO)

Set default Web Browser (GPO)

Waste Time Customizing the Start Menu and Taskbar (GPO, Script, MDT, SCCM)

Configure OEM Support info and Company Logo on Support page (GPO)

Block Windows Store and Store Apps (GPO)

Remove Store Apps during Imaging (MDT, SCCM, etc.)

Remove OneDrive from File Explorer (GPO)

Show File Extensions in File Explorer (GPO)

Show Hidden Folders and Files in File Explorer (GPO)

Show File Explorer Menu Bar (GPO)

Expand to Current Folder in File Explorer (GPO)

Customize and Push BGInfo to Desktops (script)

Customize and Push BGInfo to Desktops (GPO)

Destroy and Annihilate SMBv1 by any means necessary

TLS Configuration Guidelines (hotfix, registry, GPO)

Create and Configure a Group Policy Central Store

Updates 2.1

Add Domain User to Local Administrators Group (GPO)

Add Domain Users to Remote Desktop Users on Servers (GPO)

Modify Registry Key Permissions on Domain Computers (GPO)

Create Scheduled Tasks using Group Policy (script, GPO)

Configure PowerShell Settings using Group Policy (GPO)

Prompt for Computer Name / OSD Variable in Task Sequence (script)

Mass Upgrade Windows 10 using PowerShell (script)

Replicate MDT Boot Images to multiple WDS/PXE servers (script)

Set Google Chrome as Default Browser (GPO)

More to be added (version will be updated too)