Bulk Update Mismatched Local Accounts with PowerShell

This article provides a scripted, semi-automated solution for binding multiple JumpCloud users to their devices, when their local account names differ from their JumpCloud usernames.

For a single user-to-device binding, you can manually update the Local User Account field. See Take Over an Existing User Account with JumpCloud to learn more.

Prerequisites:

Binding Devices To User Accounts In Bulk

With the method detailed in this article, you’ll be follwing a three-part workflow that combines scripting with manual review: Run a discovery script to collect data, manually update a CSV with the correct user information, and then use a second script to perform the bulk binding.

Gathering Device And Account data

The discovery.ps1 PowerShell script is designed to automate the data-gathering process. It queries your JumpCloud environment to find devices without a bound user. The script finds potential user matches using two methods:

  • It checks the provisionerID to identify the JumpCloud user who installed the agent, if available.
  • Separately, it searches for JumpCloud users whose last name matches a local username on the device (For example, finding Henry Murphy for a local account named hmurphy).

# Discovery Script - get information about your environment
################################################################################
# TheID of the user you want to include in the search results, if left blank
# only systems w/o associations are returned
$IncludeUserID = ""
################################################################################
# Do not edit below:
################################################################################
# Get All Systems:
$systems = Get-JCSystem
# Get All Users:
$users = Get-JCUser -returnProperties firstname, lastname, email, username
# initialize an empty list
$list = @()
# counter variable for progress display
$counter = 0
# Get Systems w/o bound Users
foreach ($system in $systems) {
# display progress
$counter++
Write-Progress -Activity 'Querying JumpCloud Systems' -CurrentOperation $($system.displayName) -PercentComplete (($counter / $systems.count) * 100) -Status "Processing $($system.displayName)"
# get associations per system
$associations = Get-JcSdkSystemAssociation -SystemId $($system.Id) -Targets user
# if there are no system to user associations continue...
if (!$associations -or $associations.ToId -eq $IncludeUserID) {
# Clear Variables from previous loop
if ($usernameString) {
Clear-Variable -Name usernameString
}
if ($potentialUserString) {
Clear-Variable -Name potentialUserString
}
if ($foundUser) {
Clear-Variable -Name foundUser
}
if ($usersOnSystem) {
Clear-Variable -Name usersOnSystem
}
if ($logError) {
Clear-Variable -Name logError
}
# Gather Local User Data with System Insights
if ($system.OS -eq "Mac OS X" ) {
# Case for Mac
$usersOnSystem = Get-JCSystemInsights -systemId $system.Id -Table User | where-object { $_.Directory -match "/Users" -And $_.Username }
} elseif ($system.OS -eq "Windows") {
# Case for Windows
$usersOnSystem = Get-JCSystemInsights -systemId $system.Id -Table User | where-object { $_.Directory -match "\\Users\\" -And $_.Username }
} else {
# Case for Linux (adjust for home directory if not '/home/') $usersOnSystem = Get-JCSystemInsights -systemId $system.Id -Table User | where-object { $_.Directory -match "/home/" -And $_.Username }
}
# Build Username String
foreach ($sysuser in $usersOnSystem) {
$usernameString += "($($sysuser.Username));"
}
# trim last character
if ($usernameString) {
$usernameString = $usernameString.trimend(";")
}
# If ProvisionerID exists, get username if ($system.provisionMetadata.provisioner.provisionerId) {
$foundUser = $users | where-object { $_._id -eq $($system.provisionMetadata.provisioner.provisionerId) }
if (-not $foundUser) {
# write-host "getting user from this key: $($system.provisionMetadata.provisioner.provisionerId)"
$foundUser = [PSCustomObject]@{
Username = "NA"
}
}
}
# Build potentialUsername String
foreach ($user in $users) {
if ($usernameString -match $user.lastname) {
$potentialUserString += "($($user.firstname) $($user.lastname)::$($user.username)::$($user._id));"
}
}
# trim extra characters
if ($potentialUserString) {
$potentialUserString = $potentialUserString.trimend(";")
}
# populate the rows
$list += [PSCustomObject]@{
localAccount = "";
userID = "";
systemID = $system.id;
systemOS = $system.OS;
systemHostname = $system.hostname;
systemDisplayname = $system.displayName;
provisionerID = $system.provisionMetadata.provisioner.provisionerId;
provisionerUsername = $foundUser.Username
potentialUsers = $potentialUserString;
localUsersOnSystem = $usernameString;
}
}
}
# Write out the file discovery.csv to current working directory
$list | ConvertTo-Csv | Out-File discovery.csv

Save the discovery.ps1 script to a file of the same name and run it from your terminal:

Connect-JCOnline yourAPIKey ./discovery.ps1

The script queries your JumpCloud environment to find all devices that aren't yet bound to a user and creates a discovery.csv file with the following data points for each device:

  • systemID, systemHostname, systemDisplayname, systemOS
  • provisionerID & provisionerUsername (the ID of the user who installed the agent)
  • localUsersOnSystem (a list of all local accounts on the device)
  • potentialUsers (a list of JumpCloud users whose last name matches a local account)
localAccountuserIDsystemIDsystemOSsystemHostnamesystemDisplaynameprovisionerIDprovisionerUsernamepotentialUserslocalUsersOnSystem
  5e90f1******************Mac OS Xmdm-systemMDM System6148cd******************henry.murphy(Bind User::bind.user::5d655d******************);(Default Admin::defaultadmin::5d67fd******************);(TOTP USER::totp.user::5dd2b8******************);(Peter Parker::pparker::609442******************);(Henry Murphy::henry.murphy::6148cd******************);(Walter Parker::walter.parker::6148cd******************);(Isabella Parker::isabella.parker::6148cd******************);(Eddy Johnson::eddy.johnson::6148cd******************);(Darcy Parker::darcy.parker::6148cd******************);(Carl Murphy::carl.murphy::6148cd******************);(Aida Parker::aida.parker::6148cd******************);(Julian Murphy::julian.murphy::6148cd******************)(hmurphy);(admin);(it.support);(ian.johnson);(pparker);(totpuser)
  610b25******************Mac OS X10.15-Mac10.15-Mac6148cd******************eddy.johnson(Default Admin::defaultadmin::5d67fd******************);(Eddy Johnson::eddy.johnson::6148cd******************)(defaultadmin);(eddyjohnson)

The CSV file contains all the collected data, including the potential matches the script was able to identify.

Matching Users To Their Devices Manually

The discovery.csv file provides potential user matches, but you must review the data to ensure accuracy and to complete the process for any unmatched devices and users. For each row, your goal is to set the userID and localAccount columns.

For each row in the CSV, review the data and update the following fields:

  • userID: Enter the JumpCloud User ID of the intended user for the device.
  • localAccount: If the local username on the device differs from the user's JumpCloud username, enter the local username here.

Approach 1: Matching a user with a provisionerID

If the script identifies a provisionerUsername of henry.murphy and a localUsersOnSystem of hmurphy for the same device, you can set the localAccount to hmurphy and the userID to Henry Murphy's provisionerID.

localAccountuserIDsystemIDsystemOSsystemHostnamesystemDisplaynameprovisionerIDprovisionerUsernamepotentialUserslocalUsersOnSystem
  5e90f1******************Mac OS Xmdm-systemMDM System6148cd******************henry.murphy(Bind User::bind.user::5d655d******************);(Default Admin::defaultadmin::5d67fd******************);(TOTP USER::totp.user::5dd2b8******************);(Peter Parker::pparker::609442******************);(Henry Murphy::henry.murphy::6148cd******************);(Walter Parker::walter.parker::6148cd******************);(Isabella Parker::isabella.parker::6148cd******************);(Eddy Johnson::eddy.johnson::6148cd******************);(Darcy Parker::darcy.parker::6148cd******************);(Carl Murphy::carl.murphy::6148cd******************);(Aida Parker::aida.parker::6148cd******************);(Julian Murphy::julian.murphy::6148cd******************)(hmurphy);(admin);(it.support);(ian.johnson);(pparker);(totpuser)
localAccountuserIDsystemIDsystemOSsystemHostnamesystemDisplaynameprovisionerIDprovisionerUsernamepotentialUserslocalUsersOnSystem
hmurphy6148cd5e3ba7425ff0cc81fa5e90f1******************Mac OS Xmdm-systemMDM System6148cd******************henry.murphy(Bind User::bind.user::5d655d******************);(Default Admin::defaultadmin::5d67fd******************);(TOTP USER::totp.user::5dd2b8******************);(Peter Parker::pparker::6094427******************);(Henry Murphy::henry.murphy::6148cd******************);(Walter Parker::walter.parker::6148cd******************);(Isabella Parker::isabella.parker::6148cd******************);(Eddy Johnson::eddy.johnson::6148cd******************);(Darcy Parker::darcy.parker::6148cd******************);(Carl Murphy::carl.murphy::6148cd******************);(Aida Parker::aida.parker::6148c******************);(Julian Murphy::julian.murphy::6148cd******************)(Hmurphy);(admin);(it.support);(ian.johnson);(pparker);(totpuser)

Approach 2: Matching a user without a provisionerID

If the provisionerUsername column is blank or lists a generic account (For example, defaultadmin), you’ll need to identify the correct user on your own. You can refer to the systemHostname or other details in the file to match with the correct user in your JumpCloud directory. Enter their userID in the CSV.

localAccountuserIDsystemIDsystemOSsystemHostnamesystemDisplaynameprovisionerIDprovisionerUsernamepotentialUserslocalUsersOnSystem
  6010bf******************Mac OS Xdefaultadmins-MacBook-Air.localMacBook Air M15d67fd******************defaultadmin(Default Admin::defaultadmin::5d67fd*******************)(defaultadmin);(justin)

List of two active JumpCloud users named Justin

If the local username on the device differs from the user's JumpCloud username, update the localAccount as well.

Performing the Bulk Binding

Once you manually update the discovery.csv file, the SetUserAndBind.ps1 script will read the entire file and automate the final binding process The script reads each row in the CSV and performs the following actions:

  • It sets the Local User Account field for each user in the JumpCloud Admin Portal, if you provide a value in the CSV.
  • It then binds that user's account to the specified system. This action automatically triggers the account takeover using the information you provided.

This script includes an optional parameter, $sudoBind, which you can set to $true to grant the user administrative permissions on the device.

Warning:

The $sudoBind parameter applies to every user in the discovery.csv file that the script processes. If you set $sudoBind = $true, all users bound through this script will be granted administrative permissions on their respective devices.

# Set Local User and Bind script ################################################################################
# Bind as admin settings (default false):
# set to true if users should be bound to devices as admin
$sudoBind = $false
# Get list of users from csv in previous steps
$list = Get-Content discovery.csv | ConvertFrom-Csv
################################################################################
# Do not edit below
################################################################################

# define user bind settings
$attributes = [JumpCloud.SDK.V2.Models.GraphOperationSystemAttributes]@{ 'sudo' = @{'enabled' = $sudoBind; 'withoutPassword' = $false } }
# for each item in the list
foreach ($item in $list) {
# Add additional checks before binding
# If the UserId, and SystemID field is populated, continue...
if ($item.userid -and $item.systemID) { # If a local account was specified, update this user's localAccount name
if ($item.LocalAccount) {
# Build the request
"Adding LocalAccount Username: $($item.localAccount) on userID: $($item.UserId)"
$headers = @{
Accept = "application/json"
'x-api-key' = $ENV:JCApiKey
ContentType = 'application/json'
}
# Add the LocalAccount to the UserID
$content = Invoke-WebRequest -Method PUT -Uri "https://console.jumpcloud.com/api/systemusers/$($item.userID)" -Headers $headers -Form @{systemUsername = "$($item.localAccount)" }
# Finally Bind each user to their system if the previous IWR returns 200 response
if ($content.StatusCode -eq '200') { "Binding UserID: $($item.UserId) to SystemID: $($item.SystemId) "
Set-JcSdkUserAssociation -id $item.SystemId -userid $item.UserId -op 'add' -type 'system' -Attributes $attributes.AdditionalProperties
}
} else {
# If no localAccount name needs to be set, just bind the userID to the SystemID
"Binding UserID: $($item.UserId) to SystemID: $($item.SystemId) " Set-JcSdkUserAssociation -id $item.SystemId -userid $item.UserId -op 'add' -type 'system' -Attributes $attributes.AdditionalProperties
}
}
}

Save the SetUserAndBind.ps1 script to a file of the same name and run it from your terminal:

Connect-JCOnline yourAPIKey ./SetUserAndBind.ps1

After running the final script, your users will be bound to their systems with the correct local account names set in the Admin Portal:

Output of the bulk binding PowerShell script.

Back to Top

Still Have Questions?

If you cannot find an answer to your question in our FAQ, you can always contact us.

Submit a Case