ConfigMgr Housekeeping Scripts

ConfigMgr is a bit like a garage – you throw all kinds of stuff in there over the years, and then one day you decide to go through everything and chuck out the stuff you don’t need anymore. It’s a time-consuming process and sometimes there are difficult decisions to be made – do I / don’t I? What if it might come in useful 3 years from now?! By the time you’ve finished going through everything you’re so exhausted you start chucking out everything for fear of having to do this again one day! But once it’s done, it’s done. For now…

Recently I ran a housekeeping project for a ConfigMgr environment and ended up writing a bunch of SQL queries to help identify items that are good candidates for deletion based on various criteria. I decided to publish them on GitHub in case they might help others in their own spring-cleaning efforts.

I’d welcome any contributions as it’s challenging to identify legacy items that might exist in any environment. In the initial commit, the following queries are included:

  • Active Applications not deployed or referenced in a Task Sequence
  • Application deployments with 0 deployment results or targeted at 0 resources
  • Boot images not referenced by a Task Sequence
  • Collections with 0 members
  • Compliance Baseline deployments with 0 deployment results or targeted to 0 resources
  • Compliance Items not used in a Compliance Baseline
  • Deployed Applications with no Last Enforcement Message in the last 180 days.
  • Disabled Compliance Baselines
  • Disabled Task Sequences
  • Driver Packages not referenced in a Task Sequence
  • Enabled Compliance Baselines not deployed
  • OS Image Packages not referenced in a Task Sequence
  • OS Upgrade Packages not referenced in a Task Sequence
  • Retired Applications
  • Software Update Deployment Packages not referenced by an Automatic Deployment Rule
  • Software Update Groups not deployed
  • Standard Package deployments with 0 deployment results or targeted to 0 resources
  • Standard Packages not deployed or referenced in a Task Sequence
  • Superseded Applications
  • Task Sequence deployments with 0 deployment results or targeted to 0 resources
  • Task Sequence deployments with no execution history in the last 180 days
  • Task Sequences not deployed

There’s also a couple of PowerShell scripts to help identify orphaned content in your content source share, but use these with appropriate discretion.

https://github.com/SMSAgentSoftware/ConfigMgrCleanup

Setting the Computer Description During Windows Autopilot

I’ve been getting to grips with Windows Autopilot recently and, having a long history working with SCCM, I’ve found it hard not to compare it with the power of traditional OSD using a task sequence. In fact, one of my goals was to basically try to reproduce what I’m doing in OSD with Autopilot in order to end up with the same result – and it’s been a challenge.

I like the general concept of Autopilot and don’t get me wrong – it’s getting better all the time – but it still has its shortcomings that require a bit of creativity to work around. One of the things I do during OSD is to set the computer description in AD. That’s fairly easy to do in a task sequence; you can just script it and run the step using credentials that have the permission to make that change.

In Autopilot however (hybrid AAD join scenario), although you can run Powershell scripts too, they will only run in SYSTEM context during the Autopilot process. That means you either need to give computer accounts the permission to change their own properties in AD, or you have to find a way to run that code using alternate credentials. You can run scripts in the context of the logged-on user, but I don’t want to do that – in fact I disable the user ESP – I want to use a specific account that has those permissions.

You could use SCCM to do it post-deployment if you are co-managing the device, but ideally I want everything to be native to Autopilot where possible, and move away from the hybrid mentality of do what you can with Intune, and use SCCM for the rest.

It is possible to execute code in another user context from SYSTEM context, but when making changes in AD the DirectoryEntry operation kept erroring with “An operations error occurred”. After researching, I realized it is due to AD not accepting the authentication token as it’s being passed a second time and not directly. I tried creating a separate powershell process, a background job, a runspace with specific credentials – nothing would play ball. Anyway, I found a way to get around that by using the AccountManagement .Net class, which allows you to create a context using specific credentials.

In this example, I’m setting the computer description based on the model and serial number of the device. You need to provide the username and password for the account you will perform the AD operation with. I’ve put the password in clear text in this example, but in the real world we store the credentials in an Azure Keyvault and load them in dynamically at runtime with some POSH code to avoid storing them in the script. I hope in the future we will be able to run Powershell scripts with Intune in a specific user context, as you can with steps in an SCCM task sequence.

# Set credentials
$ADAccount = "mydomain\myADaccount"
$ADPassword = "Pa$$w0rd"

# Set initial description
$Model = Get-WMIObject -Class Win32_ComputerSystem -Property Model -ErrorAction Stop| Select -ExpandProperty Model
$SerialNumber = Get-WMIObject -Class Win32_BIOS -Property SerialNumber -ErrorAction Stop | Select -ExpandProperty SerialNumber
$Description = "$Model - $SerialNumber"

# Set some type accelerators
Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop
$Accelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
$Accelerators::Add("PrincipalContext",[System.DirectoryServices.AccountManagement.PrincipalContext])
$Accelerators::Add("ContextType",[System.DirectoryServices.AccountManagement.ContextType])
$Accelerators::Add("Principal",[System.DirectoryServices.AccountManagement.ComputerPrincipal])
$Accelerators::Add("IdentityType",[System.DirectoryServices.AccountManagement.IdentityType])

# Connect to AD and set the computer description
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$PrincipalContext = [PrincipalContext]::new([ContextType]::Domain,$Domain,$ADAccount,$ADPassword)
$Account = [Principal]::FindByIdentity($PrincipalContext,[IdentityType]::Name,$env:COMPUTERNAME)
$LDAPObject = $Account.GetUnderlyingObject()
If ($LDAPObject.Properties["description"][0])
{
    $LDAPObject.Properties["description"][0] = $Description
}
Else
{
    [void]$LDAPObject.Properties["description"].Add($Description)
}
$LDAPObject.CommitChanges()
$Account.Dispose()

Windows 10-Style Context Menu for System Tray Application

I saw a recent post by Damien van Robaeys on creating a system tray (aka notification area) app with a context menu and I was reminded of a project I’ve been working on for a while for an app which minimizes to a tray icon with a context menu – except that I wanted a Windows 10-style context menu. After all, got to keep up with the times, right?!

Take, for example, the context menu style for the Windows 10 Start:

Or in the notification area we have the Windows Security tray icon with the same context menu style:

So I set out to recreate that style in WPF and more or less managed it. Here’s an example:

The main challenges were creating the style resources in XAML for the different menu and submenu items, getting the menu to appear at the correct location, and handling the element events like mouseover and mouseleave, correctly.

It’s not perfect – the submenu still doesn’t handle quite as fluently as I would like, but it’s a pretty close style reproduction.

Below is the code to create the example menu above. You can customise the menu items in the StackPanel section of the XAML code. Just be sure to use the appropriate static resource, ie MainMenuitem, SubMenuParentitem or SubMenuitem, and use a Popup for a submenu.

Note: If you’re on a mobile device and can’t see the code below, use a desktop.

Windows 10 Upgrade Splash Screen – Take 2

Recently I tweeted a picture of the custom Windows 10-style splash screen I’m using in an implementation of Windows as a Service with SCCM (aka in-place upgrade), and a couple of people asked for the code, so here it is!

A while ago a blogged about a custom splash screen I created to use during the Windows 10 upgrade process. Since then, I’ve seen some modifications of it out there, including that of Gary Blok, where he added the Windows Setup percent complete which I quite liked. So I made a few changes to the original code as follows:

  • Added a progress bar and percentage for the Windows Setup percent complete
  • Added a timer so the user knows how long the upgrade has been running
  • Prevent the monitors from going to sleep while the splash screen is displayed
  • Added a simple way to close the splash screen in a failure scenario by setting a task sequence variable
  • Re-wrote the WPF part into XAML code

Another change is that I call the script with ServiceUI.exe from the MDT toolkit instead of via the Invoke-PSScriptasUser.ps1 as this version needs to read task sequence variables so must run in the same context as the task sequence.

I haven’t added things like looping the text, or adding TS step names as I prefer not to do that, but check out Gary’s blog if you want to know how.

To use this version, download the files from my Github repo. Make sure you download the v2 edition. Grab the ServiceUI.exe from an MDT installation and add it at top-level (use the x64 version of ServiceUI.exe if you are deploying 64-bit OS). Package these files in a package in SCCM – no program needed.

To call the splash screen, add a Run Command Line step to your upgrade task sequence and call the main script via Service UI, referencing the package:

ServiceUI.exe -process:Explorer.exe %SYSTEMROOT%\System32\WindowsPowershell\v1.0\powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "Show-OSUpgradeBackground.ps1"

To close the screen in a failure scenario, I add 3 steps as follows:

The first step kills the splash screen simply by setting the task sequence variable QuitSplashing to True. The splash screen code will check for this variable and initiate closure of the window when set to True.

The second step just runs a PowerShell script to wait 5 seconds for the splash screen to close

The last step restores the taskbar to the screen

For that step, run the following PowerShell code:

# Thanks to https://stackoverflow.com/questions/25499393/make-my-wpf-application-full-screen-cover-taskbar-and-title-bar-of-window
$Source = @"
using System;
using System.Runtime.InteropServices;

public class Taskbar
{
    [DllImport("user32.dll")]
    private static extern int FindWindow(string className, string windowText);
    [DllImport("user32.dll")]
    private static extern int ShowWindow(int hwnd, int command);

    private const int SW_HIDE = 0;
    private const int SW_SHOW = 1;

    protected static int Handle
    {
        get
        {
            return FindWindow("Shell_TrayWnd", "");
        }
    }

    private Taskbar()
    {
        // hide ctor
    }

    public static void Show()
    {
        ShowWindow(Handle, SW_SHOW);
    }

    public static void Hide()
    {
        ShowWindow(Handle, SW_HIDE);
    }
}
"@
Add-Type -ReferencedAssemblies 'System', 'System.Runtime.InteropServices' -TypeDefinition $Source -Language CSharp

# Restore the taskbar
[Taskbar]::Show()

Forcing Installation of the MDT ConfigMgr Integration WMI Classes

Today I encountered an unexpected issue installing the ConfigMgr Integration for MDT. The scenario was an environment with several SMS providers and 2 site servers in a high availability configuration (active / passive). The MDT ConfigMgr Integrations ran successfully on each of the SMS Provider servers, but on the passive site server the BDD_* WMI classes were not created under ROOT\sms\site_XYZ, even though the ConfigMgr Integration wizard completed successfully and reported no error. I ran the wizard with the option to install the task sequence actions to the local server in each case.

Without the WMI classes in place, you get the error “Failed to load class properties and qualifiers for class BDD_*** in task sequence.” when viewing or editing a task sequence containing MDT steps:

The solution was simply to manually compile the MOF file that comes with MDT, which is called Microsoft.BDD.CM12Actions.mof. After the Integration wizard has run, the MOF file be found in Program Files\Microsoft Configuration Manager\AdminConsole\bin. It can also be found in the MDT installation directory Program Files\Microsoft Deployment Toolkit\SCCM.

You need to edit the first line of the MOF file so that it is pointing to the local server, and contains the correct WMI location to install the classes to, eg:

#pragma namespace("\\SCCM001.CONTOSO.COM\root\sms\site_XYZ")

Then compile the MOF file from an admin CMD:

mofcomp <path>\Microsoft.BDD.CM12Actions.mof
BDD_* classes in WMI

Retrieving Local Logon Events from the SCCM Client WMI

Usually when querying the logon history of a Windows system you might query the Security event log or a domain controller. But if you’re using SCCM, the SCCM client also logs user logon events and stores them in WMI. Here’s a quick PowerShell script to retrieve those events and translate them into meaningful values.

You can run it against the local or a remote computer and optionally specify the maximum number of events to retrieve.

Note that for remote computers the date/time values will be displayed in your local time zone, not necessarily the timezone of the remote system.

Get-CMUserLogonEvents | Sort LogonTime -Descending | Out-GridView

Just for Fun – Send a Remote Toast Notification

Did you know you can send a custom toast notification to a remote computer? Call it poor man’s IM, but if you’re using Windows 10 with PowerShell remoting enabled it might be a good way to annoy your colleagues if you can’t find a more constructive use!

Try the following code, which creates a notification like this on your mate’s computer: