Adding a request on behalf of a community member for ITGlue integration.
They would like to see Immy being able to create configurations in ITGlue from Immy.
Was just thinking that, in addition to device configurations, if a task could further set BitLocker keys as embedded passwords on those configurations…
Currently it doesn’t but it would be pretty easy to adapt. We’re relying on Manage/Automate to create the Configurations since those ITG integrations don’t match on manually created ones and then we’d have duplicates.
Fair enough, we use N-Central for configurations, but it doesn’t always seem to work. Regardless, even having something like that for available configurations would be great!
@Stephen_Moody I would be interested in that task if you wouldn’t mind sharing. We also have a EDF in Automate with the keys that I am looking to throw to ITGlue.
If it helps anyone, here’s what we ended up doing. It’s a bit rough and could be improved with some more error handling and dupication checks. We create/update the configs in CW Manage since if you do this from the IT Glue side there are limitations with renaming computernames and updating other fields when IT Glue and CW Manage are syncing. We then update the BitLocker keys as embedded PWs in IT Glue with the second script below.
Update/Create configs in CW Manage:
# Import CWManageAPI PowerShell Module
Import-Module CWManageAPI
# Connect to ConnectWise Manage
$CWManageHeaders = Connect-CWManage -URL $CWMUri -Company $CWMCompany -PublicKey $CWMPublicKey -PrivateKey $CWMPrivateKey -ClientID $CWMClientID
Write-Host "Connected to ConnectWise Manage."
# Retrieve the mapping of TenantId to ConnectWise Manage company IDs
$PsaInfo = Get-RmmInfo -ProviderType "CWManage" -IncludeClients
[int]$TenantId = $TenantId
$PsaCompanyID = $PsaInfo.Clients | Where-Object { $_.TenantId -eq $TenantId } | Select-Object -ExpandProperty ClientID
Write-Host "Retrieved CW company ID: $PsaCompanyID for ImmyBot TenantId: $TenantId"
$script:success = $true
function New-CWMCompanyConfiguration {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ConfigurationDetails
)
$jsonBody = ConvertTo-Json -InputObject $ConfigurationDetails -Depth 10
$endpoint = "/company/configurations"
Invoke-CWMRestMethod -Endpoint $endpoint -Method 'Post' -Body $jsonBody
}
function Update-CWMCompanyConfiguration {
param(
[Parameter(Mandatory = $true)]
[int]$id,
[Parameter(Mandatory = $true)]
[string]$Operation,
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
$Value
)
# Ensure the update details are formatted as an array of patch operations
$updateDetails = @(
@{
op = $Operation
path = $Path
value = $Value
}
)
$jsonBody = ConvertTo-Json -InputObject $updateDetails -Depth 10
$endpoint = "/company/configurations/$id"
# Invoke the REST method for the PATCH request
Invoke-CWMRestMethod -Endpoint $endpoint -Method 'Patch' -Body $jsonBody
}
Function Get-CWConfigTypeId {
param (
[Parameter(Mandatory=$true)]
[string]$OperatingSystemName
)
Write-Host "Determining config type ID for OS: $OperatingSystemName"
$workstationTypeId = 24 # Managed Workstation
$serverTypeId = 25 # Managed Server
if ($OperatingSystemName -match "Windows") {
if ($OperatingSystemName -match "Server") {
Write-Host "Detected Windows Server OS."
return $serverTypeId
} else {
Write-Host "Detected Windows Workstation OS."
return $workstationTypeId
}
} else {
Write-Host "Non-Windows OS detected, skipping configuration."
return $null
}
}
Function New_or_Update_CWMConfiguration {
param (
[Parameter(Mandatory = $true)]
$computer
)
Write-Host "Processing computer: $($computer.Name)"
$lastLoginName = $computer.Inventory.LoggedOnUser
try {
$configTypeId = Get-CWConfigTypeId -OperatingSystemName $computer.OperatingSystemName
if (-not $configTypeId) {
Write-Host "Skipping configuration for $($computer.Name) due to incompatible OS type or unsupported config type."
return
}
$filterConditions = "serialNumber = '$($computer.SerialNumber)' and company/id = $PsaCompanyID"
$existingConfigs = Get-CWMObjects -Header $CWManageHeaders -EndPoint "/company/configurations" -Conditions $filterConditions
if ($existingConfigs.Count -gt 0) {
# Sort configurations by dateEntered within the _info property
$sortedConfigs = $existingConfigs | Sort-Object { $_._info.dateEntered }
# First configuration will be the oldest
$oldestConfig = $sortedConfigs | Select-Object -First 1
foreach ($config in $sortedConfigs) {
if ($config.id -eq $oldestConfig.id) {
# Update only the oldest configuration
if ($config.name -ne $computer.Name) {
Update-CWMCompanyConfiguration -id $config.id -Operation "replace" -Path "/name" -Value $computer.Name
}
if ($config.tagNumber -ne $computer.DeviceId) {
Update-CWMCompanyConfiguration -id $config.id -Operation "replace" -Path "/tagNumber" -Value $computer.DeviceId
}
if (![string]::IsNullOrWhiteSpace($lastLoginName) -and $config.lastLoginName -ne $lastLoginName) {
Update-CWMCompanyConfiguration -id $config.id -Operation "replace" -Path "/lastLoginName" -Value $lastLoginName
}
# Set oldest configuration to Active (status id 2)
if ($config.status.id -ne 2) {
Update-CWMCompanyConfiguration -id $config.id -Operation "replace" -Path "/status/id" -Value 2
}
} else {
# Set other configurations to Inactive (status id 3)
if ($config.status.id -ne 3) {
Update-CWMCompanyConfiguration -id $config.id -Operation "replace" -Path "/status/id" -Value 3
}
}
}
} else {
$newConfigParams = @{
name = $computer.Name
type = @{ id = $configTypeId }
company = @{ id = $PsaCompanyID }
serialNumber = $computer.SerialNumber
tagNumber = $computer.DeviceId
lastLoginName = if (![string]::IsNullOrWhiteSpace($lastLoginName)) { $lastLoginName } else { $null }
status = @{ id = 2 } # Assuming new configurations are Active by default
}
New-CWMCompanyConfiguration -ConfigurationDetails $newConfigParams
}
} catch {
Write-Host "Error processing $($computer.Name): $_"
$script:success = $false
}
}
# Add a switch to control whether to process all computers or just a limited number for testing
$ProcessAll = $true # Set to $false to limit processing based on $TestLimit
$TestLimit = 1
$processedCount = 0
Write-Host "Retrieving computers from ImmyBot..."
$computers = Get-ImmyComputer -IncludeOffline -InventoryKeys LoggedOnUser
foreach ($computer in $computers) {
# Check if processing all configs or if within the test limit
if ($ProcessAll -or $processedCount -lt $TestLimit) {
New_or_Update_CWMConfiguration -computer $computer
$processedCount++
} else {
break # Exit the loop if not processing all and the test limit is reached
}
}
Write-Host "Processing completed. Total computers processed: $processedCount"
# Return the overall success status
return $script:success
Create/Update BitLocker keys as embedded PWs in IT Glue:
param(
[Parameter(HelpMessage='Enter the API URI here...')]
[Uri]$ITGlueAPIEndpoint = "https://api.itglue.com",
[Parameter(Mandatory)]
[string]$ITGlueAPIKey
)
# Initialization
$script:OperationSuccess = $true # Assume success at the start
# Import ITGlueAPI PowerShell Module
Import-Module ITGlueAPI
# Add IT Glue Base URI and API Key
Add-ITGlueBaseURI -base_uri $ITGlueAPIEndpoint
Add-ITGlueAPIKey -Api_Key $ITGlueAPIKey
Function Get-Org-ID {
param (
[Parameter(Mandatory=$true)]
[string]$SerialNumber,
[string]$DeviceID
)
$configurations = Get-ITGlueConfigurations -filter_serial_number $SerialNumber
$activeConfigurations = $configurations.data | Where-Object {
$_.attributes.'configuration-status-id' -eq 704 -and
$_.attributes.'asset-tag' -eq $DeviceID
}
if ($activeConfigurations.Count -eq 0) {
Write-Host "No matching active IT Glue configuration found for the provided criteria."
return $null
} else {
$orgIDs = $activeConfigurations | ForEach-Object { $_.attributes.'organization-id' } | Sort-Object -Unique
if ($orgIDs.Count -gt 1) {
throw "Error: More than one organization ID found for the provided serial number ($SerialNumber) and device ID ($DeviceID). Operation cannot proceed."
} else {
return $orgIDs[0]
}
}
}
$Computer = Get-ImmyComputer
$SerialNumber = $Computer.SerialNumber
$ComputerName = $Computer.Name
$DeviceID = $Computer.DeviceID
$orgID = Get-Org-ID -SerialNumber $SerialNumber -DeviceID $DeviceID
if (-not $orgID) {
Write-Host "Unable to proceed without a valid organization ID."
$script:OperationSuccess = $false
return $false
}
$allConfigurations = Get-ITGlueConfigurations -organization_id $orgID -filter_serial_number $SerialNumber
$TaggedResource = $allConfigurations.data | Where-Object {
$_.attributes.'configuration-status-id' -eq 704 -and
$_.attributes.'asset-tag' -eq $DeviceID
} | Select-Object -First 1
if (-not $TaggedResource) {
Write-Host "No matching active IT Glue configuration found for serial number: $SerialNumber and asset tag: $DeviceID."
$script:OperationSuccess = $false
return $false
}
$BitLockerVolumes = Invoke-ImmyCommand {
Get-BitLockerVolume
}
foreach ($Volume in $BitLockerVolumes) {
$MountPoint = $Volume.MountPoint
foreach ($KeyProtector in $Volume.KeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'}) {
$RecoveryPassword = $KeyProtector.RecoveryPassword
$PasswordObjectName = "BitLocker Key - $ComputerName ($DeviceID) - $MountPoint"
# Prepare the password object payload
$PasswordData = @{
type = 'passwords'
attributes = @{
name = $PasswordObjectName
password = $RecoveryPassword
notes = "Bitlocker key for mount point $MountPoint on $ComputerName. Serial: $SerialNumber"
resource_id = $TaggedResource.id
resource_type = "Configuration"
}
}
# Logic to check and update/create password entry
$existingPassword = Get-ITGluePasswords -filter_name $PasswordObjectName -organization_id $orgID
$ExistingPasswordAsset = $existingPassword | Where-Object { $_ -ne $null } | Select-Object -First 1
if ($ExistingPasswordAsset.data) {
$PasswordID = $ExistingPasswordAsset.data.id
Write-Host "Attempting to update existing BitLocker password object for $PasswordObjectName."
try {
Set-ITGluePasswords -id $PasswordID -data $PasswordData -organization_id $orgID
Write-Host "Successfully updated BitLocker password object for $PasswordObjectName."
} catch {
Write-Error "Error updating BitLocker password for PasswordObjectName: $_"
$script:OperationSuccess = $false
}
} else {
Write-Host "Creating new BitLocker password object for $PasswordObjectName."
# Assuming New-ITGluePasswords is correctly implemented to handle new password creation.
New-ITGluePasswords -data $PasswordData -organization_id $orgID
}
}
}
Write-Host "Successfully processed IT Glue configurations for BitLocker keys."
return $script:OperationSuccess