Thursday, February 23, 2017

Universal SCCM Content Prestager

PS1 file can be found at my TechNet gallery: HERE

The Use Case
    Prestaging content is a fact of life in the SCCM world.  Whether you're standing up a new site, cloning a DP, or sending packages to a site with really bad bandwidth, there are a variety of reasons you need to create prestage packages.  At multiple contracts I worked, these packages were created by finding the content in the Configuration Manager GUI, right clicking, selecting Create Prestage Content File, and going through the wizard.  While this is technically a correct way to do things, it's cumbersome, requires you to remember where everything is in the menu structure, and ties up your console while you do packages one at a time.   I could see using this method for one or two packages every now and then, but you're on a PowerShell blog. Here, we're all about scale. 

Not pictured: efficiency


Making it happen
     The Configuration Manager module actually comes with a prestage cmdlet built right in, but this cmdlet is one of the most poorly written ones in all of PowerShell.  It has no real intelligence of its own, requiring you to spell out exactly what package type you want to back up. Since every package has a unique PackageID value, I never understood why they didn't just make it use that number and get on with life, but they didn't.  Feel free to download the script and follow along.
I actually couldn't fit them all on one screen


The Script
     The first thing we need to do is declare our variables. This script needs to know what the package ID number is (which can be found literally everywhere the package is mentioned in the SCCM console or WMI interface),  what DP we're pulling the content from, and where the file needs to be saved. All of these are mandatory, so you'll be prompted for them if you don't put them inline.  Also, depending on the prestage location, you'll need admin rights to move files there, so we just check for those at the outset.  We'll also make sure you're connected to your CMSite PSDrive. I also have the script make sure the package isn't already there.  Something else to keep in mind is that the DP name needs to be a FQDN when the command runs. If you enter it with just the hostname, the script will sort that out for you, so no worries.



     After all the pre-reqs have checked out, the real logic comes in. The first thing it does is try to run get-cmpackge with the package ID, if that comes back as $NULL, meaning there was no package with that ID, it starts down the list of available content types. It checks if it's a software update package, application, boot image, OS image, or driver package.  Until it finds something that actually returns an object, it'll keep checking. Then, it makes a mental note of what it found so we can use it later.  For troubleshooting and debugging reasons, I like to output the name of the package as well as its source path, but this isn't necessary. It will also tell you where it's going and what it will be named.  Then, we use a Switch  statement with the count variable (our mental note from earlier) to select the correct Publish-CMPrestageContent flags that actually do the prestaging work for us.

Figuring out what you wanted


Actually prestaging it




Actually Using It
     On its own, it's a nice function to have loaded in my shell. Being able to generate a prestage file without tying up my GUI is always handy, and depending on the organizational skills of the previous SCCM admins, not having to dig around to find a package in their menus can be a real time saver.  Where this script comes into its own, however, is when it's chained together with other commands, which is what we'll discuss in our next post. 

Tuesday, February 21, 2017

2-21-2017 - Working with CSV Files


Hey everyone, it's been a little while since my last post, but work's been busy.  A recurring issue I see on TechNet is that plenty of people have trouble working with importing/exporting CSV files.  Specifically, I see questions about how to modify CSV files. Personally, I don't see any value in modifying the CSV file directly, but rather importing the data from said file and working with the dataset natively in PowerShell.  Using one person's thread as an example, he had a CSV file full of IP addresses that corresponded to his VM's.  He wanted to add data about the VM based on the IP address, but wanted to know how to "modify the line" in the CSV file.    He had already written part of the script to create the CSV by pinging each computer in his IP range and outputting that to CSV.  For our test, here's what our CSV looks like:
Now, let's say we want to find the hostnames for each of these computers and add that to a new column. There are a couple ways we could do this, but in an enterprise environment, a good way to do it is to just ask AD who it is.   First, we create the empty "column" for our Hostname value.





Once that's created, we need to fill it with values:


















Keep in mind that you will see a lot of red text for any computer that doesn't have an entry in AD. This could be a printer, a switch, etc.  If you're using Windows 8.1+ or Server 2012R2+, you can use the resolve-dnsname cmdlet. 

You can add more columns to the CSV by using the Add-Member cmdlet to your heart's content, and when you're done, you pipe your $dataset variable back to the export-csv cmdlet.   

Really, once you start looking at CSV files as arrays of custom objects, they're pretty easy to work with.

Friday, February 10, 2017

2-10-2017: Importing drivers into SCCM in bulk

This is taken from my TechNet gallery here: https://goo.gl/n1QT89

     When you're tasked with something like a Windows 10 upgrade, you'll find yourself spending lots of time downloading and importing drivers into SCCM.   While this script won't go out and download them for you (like the Dell and HP Driver Import tools I've seen out there), it manufacturer, model, and architecture agnostic, you don't get caught up trying to negotiate your way past your firewall and proxy teams, and it runs in a bit under 50 lines of code (including comments). Rather than pasting in the entire thing, I'll do a screenshot and walk through from there.

     For this script to work, there's some groundwork required on your part. When you download the drivers, they need to be downloaded into a folder that has whatever name you want for your driver package later.  If you're like me, you're already doing this as you download. If I need drivers for an HP Z230 desktop, the folder they're saved in is already called "HP Z230 Windows 10 x64" or something similar so I can find them later.  The way this script works, whatever your folders' names are is what names your driver packages will end up with.
    Aside from that, all you need to do is plug in the path to the file share that has all your make/model folders in the root, as well as the location where you want to store your driver packages.
    Something you will notice in this script is that I bounce between my C:\ drive and my SCCM drive. This is because UNC paths don't always work as expected when you're on the SCCM drive, and SCCM cmdlets don't play nice running from anything other than the SCCM drive.  To guarantee they both work when needed, I just switch between locations, and it's no big deal. 
    This script can take a little while to run, but it will give you feedback as it goes, and it doesn't lock you out of the SCCM GUI while it runs.

Monday, February 6, 2017

02-06-2017: SCCM Powershell Tools

I'll get this out of the way right now: I think SCCM's PowerShell module is garbage.  It's slow, it doesn't work like you'd expect a lot of the time, and it's got absolutely nothing on the ease of use and functionality of something like the ActiveDirectory module.  Thankfully, WMI is still a thing, and that gives us an excellent way to interact with SCCM via PowerShell. With that in mind, here's some quick and dirty (but still useful) PowerShell functions I've put together. Most, if not all, PowerShell tools I've written for SCCM can also be done with SQL queries and commands, so if that's more your thing, have at.

Find-ClientByMac
File can be downloaded from my technet gallery here
This is a simple enough tool. It will find any clients in the SCCM database that match a "like" query against the MAC address you provide.  This is handy for finding duplicate objects with the same MAC, which can royally screw with trying to PXE boot and image a computer.

Function Find-ClientByMAC ($inputMac){
    $namespace = "root/SMS/Site_TST"
    $siteServer = "testServer"

    $inputMac = $inputMac.Replace(" ","")
   $inputMac = $inputMac.replace("-",":")
   
    if ($inputMac -notlike "*:*")
    {
        $count = 0
        while ($count -lt ($inputMac.Length - 2))
        {
            $inputMac = $inputMac.Insert(($count)+2,':')
            $count += 3
        }
        $inputMac
    }
    Get-WmiObject -Namespace $namespace -ComputerName $siteServer -class SMS_R_System -filter "MACAddresses like '%$inputMac%'"
 }


Something to keep in mind with SCCM is that it stores MAC addresses with ":" between each pair, but your computer's IPConfig command will give it to you with "-"'s.  This will replace a - with a :, and if you just feed it a straight set of text, it will insert the ":" between each pair.
This will find any computer that matches the MAC you provided and returns with a WMI object of the SMS_Device class.  If you want to delete the duplicate objects, you can simply pipe to the remove-wmiobject command to delete.

Thursday, February 2, 2017

02-02-2017 - Finding Empty Device Collections in SCCM

     Today's script is going to be almost a one-liner, but a very useful one.  I had found SQL queries that would do this same thing, but I frequently find myself working on a computer that does not have the SQL management software installed on it, and this query doesn't run cleanly through the SCCM Management software.  However, I'm always on a computer with PowerShell and since this runs through WMI, you don't even need to connect to the SCCM site code drive.
     Having the right amount of device collections in SCCM is a bit of a balancing act. You don't want so few collections that you don't have a good way to sort out your clients logically, but you don't want so many that you've gunked up your database with entries you're never actually going to use. On top of that, some of the companies I've contracted with have had many collections that never held a single client. This script will go through and find those empty collections for you.   If you just want the PS1 file, click HERE.

Step 1: Initializing a few variables
Typically, if I'm working with SCCM through WMI, I like to setup a few variables in my shell right away to make my life easier and save some keystrokes throughout the day.
$siteServer = "PriServer1" # whatever the name of your primary server is
$namespace = "root/SMS/site_TST" # root/SMS/site_  whatever your site code is

If you're constantly making WMI calls to SCCM, being able to just enter those variables instead of typing it all out can make things easier. At least, that's what I've found. 

Step 2: Get your SCCM Device Collections
This script is going to be done on a single line, but I'll break it out into chunks first. The first thing we need to do is pull all of the device collections from SCCM.
Get-WmiObject -Namespace $namespace -ComputerName $siteServer -Class SMS_Collection -filter 'CollectionType =2'
 
If you wanted to pull user collections, you would change 'CollectionType = 2' to 'CollectionType = 1'
If you want to get both sets of collections, just leave out the -filter portion entirely.

Step 3: Check Those Collections for Members
There are a couple ways you could do this. You could pipe your collections to a ForEach loop with an if statement in it. Alternatively, I choose to just pipe the first cmdlet into a where statement that does it for me.
| where {$colID = $_.CollectionID;(Get-WmiObject -Namespace $namespace -ComputerName $siteServer -class SMS_FullCollectionMembership -filter "CollectionID='$colID'").count -eq 0}

What this is doing is setting a variable called "$colID" to be the collection ID number from the collection passed through the pipe.  Then, we're going to do a WMI query to the SMS_FullCollectionMembership class, which is a class that maintains a list of every device/user to collection association. We're adding a filter to only show collection IDs that match the one we want, counting up all the members, and if that number equals 0, we know the collection has nothing in it.

Step 4: Profit
If you just stop there, you have a set of list-formatted data that, while not pretty, does contain all the information and objects you need to do work. It's not the easiest thing to read, but it does give you a very solid information dump about the empty collection that looks something like this:

























Chances are, if you're generating this report for a manager, customer, etc, they're going to want it formatted a bit nicer, and they'll probably want it in an Excel spreadsheet.  The easiest way to do this is:
| select Name,CollectionID | export-csv C:\users\MyUser\Documents\EmptyCollections.csv -noTypeInformation

 Alternatively, if you get the go-ahead to remove these device collections, instead of piping to a select/export-csv statement, you can pipe to remove-wmiobject instead. 
For the love of god, run it with -whatif first.  

Thanks for reading, and if you have any questions, feel free to post them in the comments.