A little corner of the Empire on the web.

Showing posts with label SCCM. Show all posts
Showing posts with label SCCM. Show all posts

16 April, 2019

Retrieving Package Source Path/Application Content Locations from SCCM

We've been running with an old file server and a new file server for our SCCM package sources for a while now, with all the new software being put on the new server, and obsolete software being deleted from the old server as it goes out of use. Over time the old server should be used less and less as a package source, until it falls out of use and is decommissioned, but we really need to get a shift on with getting everything off the old hardware, and onto the new one. To make things easier for future moves the new one's referenced in all our systems with a fairly generic DNS name (rather than an old and obscure machine naming scheme), which should also make future server changes easier to manage.

So the first thing that I need to do is trundle through SCCM and work out what software is still using the old server as its source. There are a few main categories of bulk content that could be referencing files on that old server:

  1. Programs (using the Package Model)
  2. Driver Packages
  3. Applications (using the Application model)

Any of those could have their content files stored on either server. Packages are fairly easy as you can add a column to the console called "Package Source Path" and then sort by that. Applications are not so easy; each Deployment Type within an app has its own individual source path, and you have to drill right into the Deployment Type's properties to see it, there's no quick and easy way to see a list. (Obviously things like OS Images, Boot Images, etc could also be on the old server but we've only got single digit numbers of each of those, so they're just as quick to check one by one).

Packages (and Programs)

First the easy options, Packages and Driver Packages. Packages have one Package Source Path for the whole Package, which is used by each Program in the Package. All we need to do is use a cmdlet to retrieve all of the packages, then just pull out the properties from each that we're interested in, and finally dump that to a file that we can open in Excel and work our way through:

(All of the below PowerShell commands to be executed from a Config Manager PowerShell window, or a normal PowerShell command prompt with the ConfigurationManager module loaded and the current location changed to the Config Manager PSDrive for your site).


PS ABC:\> $Packages = Get-CMPackage
PS ABC:\> $Packages | Select-Object -Property PackageID, Name, PkgSourcePath | Export-Csv -Path C:\temp\PkgSrc.csv -NoTypeInformation

Driver Packages

These are very, very similar to normal packages. So similar that you may not even notice there's six extra letters in the initial cmdlet.


PS ABC:\> $DriverPackages = Get-CMDriverPackage
PS ABC:\> $DriverPackages | Select-Object -Property PackageId, Name, PkgSourcePath | Export-Csv -Path c:\Temp\DriverPkgSrc.csv -NoTypeInformation

Both of the above (Get-CMPackage and Get-CMDriverPackage) are relatively quick, they took less than a minute even running them from a remote machine against a site with a large number of [driver] packages .

Application model applications

Ok, here we go, here's the big one. Each Application can have multiple Deployment Types, each of those deployment types (which can each use a different deployment style) has its own, individual Content Location. To add to this, these queries are slower than their equivalents in the Package model (we're talking multiple minutes rather than seconds), long enough that there's some progress text output during this one, just to let you know that it's actually doing something. Then finally details of the app's Deployment Type(s) are all listed in a big XML property of the Application object, rather than each being their own object properties.

Here we are. Firstly we get the full list of apps from the site (which is the part that I've seen take 10 to 15 minutes on a site with around 800 app model apps), then iterate over them, pulling the relevant properties out of the XML (including iterating over each Deployment Type in that app), then finally output all of that as an object which we write to a CSV file:


$CSVOutPath = 'c:\temp\AppSrc.csv'
#Change to your site's ConfigMgr PSDrive before running this script

#Get each Application, then get the source path from each Applications's Deployment Types
Write-Host "Fetching application details - this takes a few minutes, please wait...`n"

$Applications = Get-CMApplication
$AppCount = $Applications.Count
Write-Host "$AppCount applications found.`n"

#Iterate over the apps list pulling out the details for each app. Takes a couple of minutes.
$i = 1
$AppSourceList = ForEach($App in $Applications)
{
    Write-Progress -Activity 'Checking apps and deployment types' -Id 1 -PercentComplete $(($i / $AppCount) * 100) -CurrentOperation "app $i / $AppCount"
    $PackageXml = [xml]$App.SDMPackageXML
    #An app can have multiple Deployment Types, each with their own source location. DT details are stored in the XML properties
    ForEach($DT in $PackageXml.AppMgmtDigest.DeploymentType) {
        $DtTitle = $DT.Title.'#text'    #need to quote property names with hashes in them, normal backtick escaping doesn't work
        $DtTech = $DT.Technology
        $DtLocation = $DT.Installer.Contents.Content.Location
        New-Object -TypeName psobject -Property (@{AppDisplayName = $App.LocalizedDisplayName; PackageID = $App.PackageID;
        CiId = $app.CI_ID; Enabled = $app.IsEnabled; Superseded = $app.IsSuperseded; HasContent = $app.HasContent;
        DepTitle = $DtTitle; DepTypeTech = $DtTech; DepTypeSrcLocation = $DtLocation})
    }
    $i++
}

#$AppSourceList | Out-GridView
$AppSourceList | Export-Csv -Path $CSVOutPath -NoTypeInformation

Download the Get-ApplicationSources.ps1 here: GitHub Get-ApplicationSources.ps1

(No download for the Package and Driver Package sources, as they're basically just one-liners).

09 April, 2019

SCCM Detection Rules for Dell BIOS Updates

I've been doing a bit of work lately on deploying Dell BIOS updates using SCCM. This is using SCCM's Application Model to deploy the updates out to live machines, and also using those to update BIOS firmware during OSD Task Sequences. I've been a through a few versions of SCCM app detection rules for these jobs. The problem is that WMI's SMBIOSBIOSVersion is a text field, so it's returned as and then treated as a string type. There's also nothing that standardises how manufacturers fill in that text field, so they can use whatever versioning scheme they want, with whatever text they want around it. So different manufacturers do it different ways and they often change it between models.

Dell (at least for their Optiplex and Latitude business products) seem to use two different BIOS version number schemes at the moment, they seem to use one on older models, and the other for newer models, and they each have their own challenges. The first scheme uses an "A" followed by a two digit integer, counting up from zero, eg "A00" followed by "A01" then "A02" with "A09", "A10", "A11" being later in the series. The second scheme is a three part version number separated by dots (full stops, periods whatever your region calls them), eg "1.0.0", then "1.1.0" and later releases may be "1.6.2", "1.9.3", "1.11.2".

Taking them one by one, the problem with the "A00" scheme is that it's a mix of letters and numbers, so you can't do a purely numeric comparison to get the larger number, but at least a string greaterThanEqualTo (-ge) comparison of them does work, and you can always remove the leading "A" and then just do a numerical comparison.

Unfortunately the "1.0.0" type scheme works perfectly with a string greaterThanEqualTo (-ge) comparison right up to point where any of the numbers in it become double digits, so '1.2.0' -ge '1.0.5' returns True, whereas '1.12.0' -ge '1.3.5' unexpectedly returns False. This is because it's comparing each digit in the number one by one, so the first digit "1" is the same in both, then the dot is the same in both, then "1" is less than "3", so the greater than test fails, even though 12 is bigger than 3 it only looks at the digits individually.

The end result of this is that I'm now using the following PowerShell script in a script detection rule for Dell's BIOS updates. You just put the version number of the BIOS version that you're deploying in the variable in the first line then it parses it all correctly from that.

$NewBiosVer = '1.13.0'
$CurrentBIOSVer = (Get-WmiObject -Class Win32_BIOS).SMBIOSBIOSVersion
#Check if this is old-style Dell BIOS versioning "Ann" or new-style "n.n.n"
If ($NewBIOSVer.StartsWith('A') -and $CurrentBIOSVer.StartsWith('A')) {
    If ($CurrentBIOSVer -ge $NewBIOSVer) {
        #To indicate software is found, return something in STDOUT
        Write-Host "BIOS Version $CurrentBIOSVer found"
    } else {
        #To indicate software not found, don't return anything to STDOUT or STDERR
    }
} ElseIf (($NewBiosVer -match "^[\d\.]+$") -and ($CurrentBIOSVer -match "^[\d\.]+$")) {
    #Check if we've got a problem with two digit decimals (where 1.9 is less than 1.10 in version numbers, but not numerically)
    $NewBiosVerArray = $NewBiosVer.Split('.')
    $CurrentBIOSVerArray = $CurrentBIOSVer.Split('.')
    If ([int]$CurrentBIOSVerArray[0] -ge [int]$NewBiosVerArray[0]) {
        If ([int]$CurrentBIOSVerArray[1] -ge [int]$NewBiosVerArray[1]) {
            #To indicate software is found, return something in STDOUT
            Write-Host "BIOS Version $CurrentBIOSVer found"
        }
        Else {
            #To indicate software not found, don't return anything to STDOUT or STDERR
        }
    } else {
        #To indicate software not found, don't return anything to STDOUT or STDERR
    }
} Else {
    #Doesn't fit either BIOS version format???
    #To indicate software not found, don't return anything to STDOUT or STDERR
}

For the task sequence step's run conditions I'm doing two WQL queries, one for the device's model, and the second a simple check that the BIOS version is not the one that the update's looking for (so that machines on our nominated version skip the step) and then the Application's detection rules check fully whether the currently installed version is older or newer than the one we're installing (using the above script).

11 July, 2016

Find objects within folders in the SCCM Console hierarchy using PowerShell

Frustration


Ever since Microsoft's SCCM got the new-style System Centre console with SCCM 2012, I've been massively frustrated about how difficult it is to find items in the console once you've organised it with folders. Probably not too much of a problem if you're a lone SCCM admin, but as someone who works as part of a larger team who all use SCCM for slightly different reasons it can be a nightmare finding things like collections buried deep in the folder hierarchy.


Yes, I know that the console has a search function, and I know that there's a subtle "All Subfolders" button that magically appears on the Search ribbon bar sometimes. However that "All Subfolders" button has a few major flaws: 1) it doesn't always appear when it ought to, leaving you in a dance of clicking in the search text box, clicking away, clicking back in there, clicking away to another part of the console, clicking back, all the while crossing your fingers that the button will show up; 2) annoyingly clicking the "All Subfolders" button clears out any text that you may have foolishly typed into the search bar already before you remembered that you wanted to search the whole tree; 3) most importantly, the search results don't give you any information about the folder path that it found each item on, so it doesn't help you find that item (or similar items that may be in the same folder) quickly next time.


SCCM console rant over.


Journey


So over time I started casually looking around for ways to access this folder hierarchy info. These days there's a regularly updated set of SCCM PowerShell Cmdlets provided by the ConfgMgr team, this seemed an obvious place to start. Unfortunately, none of them even acknowledge that console folders exist. Next I set off on a hunt through WMI on the site server (using one of my favourite tools WMI Explorer (WMI Explorer downloads), but I couldn't any folder related properties on the collections/packages/etc themselves and couldn't find any obvious looking WMI classes. Real desperation now, open up SQL Management Studio and hunt through the Views in the site database. Nothing obvious. Brick wall hit. I went away and worked on other stuff for a while.


Enlightenment

Then early last week I came across this blog post by Peter van der Woude, that my previous searches had somehow missed: More than just ConfigMgr: Get the folder location of an object in ConfigMgr 2012 via PowerShell. Finally, answers - not only did that post contain this magic phrase: A good thing to know is that all folder information is stored in two classes in WMI, SMS_ObjectContainerNode and SMS_ObjectContainerItem. These two classes are the same for every object type., but it also had a working script linked, find his script in the TechNet gallery here: TechNet Gallery: Get the location of an object in the console via PowerShell


Moving Beyond


As great and useful as Peter's script was, it does have a minor flaw, and doesn't handle some edge cases. So I set off to use his information to write my own. The minor flaw was that his script requires hard coding your SMS Site Code and Site Server names into it. I can see the reason for that, especially as it means that his script has no external dependencies (other than the ConfigMgr infrastructure, obviously) but it still bugged me. More problematically, I hit an edge case that it didn't handle within my first few uses of it. Some objects (of different types) in SCCM can share the same SCCM IDs. So a Collection, a Query and a Package could have the same 8 character ([site code][5 hex digits]) ID, as all of those objects have their own numbering starting from XXX00001 and counting upwards. So using Peter's script, you don't always find the object that you're actually looking for, and, as his doesn't echo the name or type of the object that it's showing the hierarchy for, it can get quite confusing if it's not showing the info for the item that you're expecting.


Nirvana (or Farvana)


So, I've written my own PowerShell function to do this: Get-SCCMObjectLocation, find the code on GitHub, here GitHub: Get-SCCMObjectLocation.ps1. There's a bit more code here than Peter's versions, but this now outputs the object's name and type (so that you know you haven't made any typos, and you really have found the droids object you were looking for), and if there are multiple objects with the same ID, it lists them one by one. It also gets the site code and site server name from the ConfigurationManager PowerShell provider, though this does mean that you need the SCCM console installed on your machine, and you need to change to ConfigurationManager's PSDrive before running it.


Usage:
PS C:\>Get-SCCMObjectLocation -SMSId "ABC001BB"
root\Racked Server Drivers\Dell\PE1950-Microsoft Windows 2008 R2 SP1-OM7.3  [SMS_DriverPackage]
PS C:\>"ABC000CC" | Get-SCCMObjectLocation
WARNING: Multiple objects with ID: ABC000CC
root\x64\Test\Win 7 Ent x64 with Office 2010  [SMS_ImagePackage]
root\Servers\Server roles\All Domain Controllers  [SMS_Collection_Device]
PS C:\>Get-SCCMObjectLocation -SMSId "ABC00166" -SiteCode ABC -SiteServerName sms01.example.com
root\Application Deployment\MS Access App-V  [SMS_Collection_User]

Download

Download the script here: GitHub: Get-SCCMObjectLocation.ps1



Music to get through this with: Faithless - Faithless 2.0