Use Powershell to match the site and building info for all machines

coreythomas
New Contributor III

I am an SCCM guy. We'll get that out first. :)
Our environment has multiple sites across the US with local IT staff in each. We wanted to grant them access to the JSS but restricted them based on their site. The problem is that machines are not automatically assigned to a site, even though we can attach subnets to a building and a building to a site (sort of). So even though JSS knows a machine is on a subnet in a building that is in a site, it will not update the site for that machine. That means the smart computer groups that are limited by site will not show any machines until that machine is also updated to that site...

So I put together a powershell script that uses the API to pull all computers, see if the building and site match, if not, it compares the building to the site list and if it exists, it sets it. This makes the flow much more manageable for larger environments (and really, this should be built in...).

The only problem is that the smart groups do not re-evaluate until their normal schedule. If there is an API call to kick JSS into re-evaluating then please let me know so I can add it. Otherwise, the smart groups will update when the machines check in or a manual recon command is ran.

All you need to do for this script is add a new standard account for JSS API calls and modify the URL, user, and pass. Then run and done. All your computers that have a building that is also a site will be updated.

Script:

#========================================================================
# Created with: SAPIEN Technologies, Inc., PowerShell Studio 2012 v3.1.35
# Created on:   12/14/2015 2:16 PM
# Created by:   Corey Thomas
# Organization: Removed for privacy
# Version :   1.0  
#========================================================================

### Pre-reqs:  Create a standard user account for JSS (full access, custom privileges) and grant it read/update to the JSS Objects.  Specify this account below

$JSSAPIURL = "https://yourJSSURL:8443/JSSResource"
$JSSAPIUser = "JSSApi"
$JSSAPIPass = "JSSAPIpassword"
$VerbosePreference = "SilentlyContinue"  #Optional for extra logging - Change to "Continue"


#First we need to setup the shell to ignore self-signed certs for non-PKI Casper installs:
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

#Next we setup the creds we will be using:
$user = $JSSAPIUser
$pass = ConvertTo-SecureString -String $JSSAPIPass -AsPlainText -Force
$Creds = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $user, $pass

#First, let's get a list of the sites from JSS
$url = "$JSSAPIURL/sites"
$sites = Invoke-RestMethod -Uri $url -Credential $Creds
#The data is stored in $sites.sites.site

#Get a list of all computers:
$url = "$JSSAPIURL/computers"
$computers = Invoke-RestMethod -Uri $url -Credential $Creds

#Setting up a template for XML update into the JSS API
[xml]$Template = "<?xml version='1.0' encoding='UTF-8'?><computer><general><site><id></id><name></name></site></general></computer>"

#Loop through the data and start pulling data:
foreach($machine in $computers.computers.computer){

    #Now we setup the URL to pull data from
    $computerID = $machine.ID
    $computerName = $machine.Name
    $url = "$JSSAPIURL/computers/id/$computerID"
    Write-Output "Getting data for $computerName"
    Write-Verbose "ID: $computerID"
    Write-Verbose "API: $url"

    #Clear the data variable to ensure we don't accidentally overwrite the wrong data
    $data = $null

    #Now we call the JSS REST API to get the data
    $data = Invoke-RestMethod -Uri $url -Credential $Creds

    #Pull the current Site data
    $currentSite = $data.computer.general.site.name
    $building = $data.computer.location.building
    Write-Verbose "Current site: $currentSite"
    Write-Verbose "Current building: $building"

    #See if that building exists in the site list and update the site to match:
    if($sites.sites.site.name -contains $building){
        Write-Verbose "Building $building exists in the JSS Sites"
        if($currentSite -notmatch $building){
            Write-Verbose "Site and building do not match in JSS"
            #Get the ID of the site (get the index of the name and then use the index to pull the ID)
            $SiteID = $sites.sites.site.ID[[array]::indexof($sites.sites.site.name,$currentSite)]
            Write-Verbose "Site ID: $SiteID"
            #To change data, we simply update XML and then send it back to JSS REST API.
            Write-Host "Updating computer to match site"

            ### OLD Method, this had the potential to be more destructive since it sent the entire computer data back to the JSS API
            ###  so I switched to using a template to ensure that only the site data is sent back.
            ### $data.computer.general.site.id = $SiteID
            ### $data.computer.general.site.name = $building

            $newData = $template
            $newData.computer.general.site.ID = $SiteID
            $newData.computer.general.site.name = $building

            $return = Invoke-RestMethod -Uri $url -Credential $Creds -Method put -Body $newData

        }else{Write-Output "Site and building match, no changes made"}
    }else{Write-Output "Building does not exist as a site, no changes made"}

}
6 REPLIES 6

freddie_cox
Contributor III

Great info @coreythomas! Just throwing a shout-out from a fellow Powershell & CasperAPI fan. The Invoke-RestMethod commandlet makes working the the API a breeze.

In the future you can try using three backticks ``` to enclose code at the begining and end so that the markdown doesn't try to change the formatting. Eg:

```
# Writing output so I know whats going on
Write-Host "This is a code block"
```

Will get formatted like the line(s) below.

# Writing output so I know whats going on
Write-Host "This is a code block"

Keep up the good work and keep sharing the "power" of powershell!

loceee
Contributor

Well I guess posting Powershell to JAMFnation is allowed :P

coreythomas
New Contributor III

Thanks Freddie! It's good to see that I'm not the only Powershell guy here! I updated my code post and that looks soooo much better. :)

Regarding the smart groups, I noticed that the smart groups will not update until the machine checks in, or a manual sudo jamf recon command.

However, during my testing it did update automatically a few times. I'm thinking that a newer timestamp in the computer data when it resubmits back to the API might trigger it. For the version of the script posted above, it only submits the site data changes and some of my testing was resubmitting the full computer data back. I'm gonna test out a timestamp change sometime today. If that works, I'll update the script here.

freddie_cox
Contributor III

Now that looks (and reads) much better @coreythomas!

mm2270
Legendary Contributor III

Hey @coreythomas What version of the JSS are you guys on? The reason I ask is because some versions back there was a defect that was exactly as you described, with Smart Groups not auto recalculating group membership when they were updated thru the API. I ran into this myself, and knew something was amiss, so I reached out to our JAMF account rep and he confirmed that Smart Groups should absolutely recalculate membership if criteria is adjusted through the API. He was able to recreate the issue and the defect was addressed a couple of revisions later.
I also discovered while troubleshooting this that the simple act of opening the Smart Group in the JSS UI and just clicking Edit, then Save (no changes) also did the recalculation.

So, you don't happen to still be on a much older 9.x version do you? If you are pretty current and seeing this, I would suggest talking to JAMF to see if this is a bug that has resurfaced. Hopefully not, but it would not be the first time and old defect made a reappearance.

roiegat
Contributor III

@coreythomas

Hoping you can lend a hand in a script I'm working on. Short version of this is were trying to create a powershell script for the access team to add a active directory role for a mac. The first part is done in AD, the second part (which is what I'm working on) needs to simply add the computer name to a group. That way a policy sees a new computer name in it and runs a script to finalize the process.

I've used your script as the basis and I got all the infromation. Currently I have it pretty much create an XML document with all the information and need to upload it to Casper, but really not sure how to do that....here's what I have so far:

### Pre-reqs:  Create a standard user account for JSS (full access, custom privileges) and grant it read/update to the JSS Objects.  Specify this account below

$JSSAPIURL = "https://mycompany:8443/JSSResource"
$JSSAPIUser = "XXXXXXXXXXXXXX"
$JSSAPIPass = "XXXXXXXXXXXXXX"
$JSSGroupName = "TestAPI"
$MachineTNumber = "COMPUTERNAME"
$VerbosePreference = "SilentlyContinue"  #Optional for extra logging - Change to "Continue"


#First we need to setup the shell to ignore self-signed certs for non-PKI Casper installs:
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

#Next we setup the creds we will be using:
$user = $JSSAPIUser
$pass = ConvertTo-SecureString -String $JSSAPIPass -AsPlainText -Force
$Creds = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $user, $pass


#Get group info:
$url = "$JSSAPIURL/computergroups/name/$JSSGroupName"
$groupinfo = Invoke-RestMethod -Uri $url -Credential $Creds
$groupnumber = $groupinfo.computer_group.id
$groupname = $groupinfo.computer_group.name
$groupsize = $groupinfo.computer_group.computers.size


#Get Computer info:
$url = "$JSSAPIURL/computers/name/$MachineTNumber"
$computerinfo = Invoke-RestMethod -Uri $url -Credential $Creds
$compid = $computerinfo.computer.general.id
$compname = $computerinfo.computer.general.name
$compmacadd = $computerinfo.computer.general.mac_address
$compaltadd = $computerinfo.computer.general.alt_mac_address
$compserial = $computerinfo.computer.general.serial_number


##create clone to append to data
$element=$groupinfo.computer_group.computers.computer[0].Clone()
$element.id = $compid
$element.name = $compname
$element.mac_address = $compmacadd
$element.alt_mac_address = $compaltadd
$element.serial_number = $compserial
$groupinfo.computer_group.computers.AppendChild($element)

##Since the size didn't automatically add one...did some math to do it
$groupnewsize =1 + $groupinfo.computer_group.computers.size
$groupinfo.computer_group.computers.size="$groupnewsize"

##need to upload $groupinfo to Casper now

Some attributes have bene changed to proctect the innocent. The computer name is quite different but for security reasons, I changed it here. In the final version, the computer name will be inputed by the person running it...but for right now I have it in there for testing.

Also, this is only my second day in Powershell....so go easy on me. I can do this in a bash script pretty easily, but the Access guys need it in a powershell script.

Any advice you can give would be great.