diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 index 7a8044bc1c5f..88fb8492350e 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 @@ -7,13 +7,42 @@ function Get-NormalizedError { param ( [string]$message ) + + #Check if the message is valid JSON. + try { + $JSONMsg = $message | ConvertFrom-Json + } catch { + } + #if the message is valid JSON, there can be multiple fields in which the error resides. These are: + # $message.error.Innererror.Message + # $message.error.Message + # $message.error.details.message + # $message.error.innererror.internalException.message + + #We need to check if the message is in one of these fields, and if so, return it. + if ($JSONMsg.error.innererror.message) { + Write-Host 'innererror.message found' + $message = $JSONMsg.error.innererror.message + } elseif ($JSONMsg.error.message) { + Write-Host 'error.message found' + $message = $JSONMsg.error.message + } elseif ($JSONMsg.error.details.message) { + Write-Host 'error.details.message found' + $message = $JSONMsg.error.details.message + } elseif ($JSONMsg.error.innererror.internalException.message) { + Write-Host 'error.innererror.internalException.message found' + $message = $JSONMsg.error.innererror.internalException.message + } + + + #finally, put the message through the translator. If it's not in the list, just return the original message switch -Wildcard ($message) { 'Request not applicable to target tenant.' { 'Required license not available for this tenant' } "Neither tenant is B2C or tenant doesn't have premium license" { 'This feature requires a P1 license or higher' } 'Response status code does not indicate success: 400 (Bad Request).' { 'Error 400 occured. There is an issue with the token configuration for this tenant. Please perform an access check' } '*Microsoft.Skype.Sync.Pstn.Tnm.Common.Http.HttpResponseException*' { 'Could not connect to Teams Admin center - Tenant might be missing a Teams license' } '*Provide valid credential.*' { 'Error 400: There is an issue with your Exchange Token configuration. Please perform an access check for this tenant' } - '*This indicate that a subscription within the tenant has lapsed*' { 'There is no exchange subscription available, or it has lapsed. Check licensing information.' } + '*This indicate that a subscription within the tenant has lapsed*' { 'There is subscription for this service available, Check licensing information.' } '*User was not found.*' { 'The relationship between this tenant and the partner has been dissolved from the tenant side.' } '*The user or administrator has not consented to use the application*' { 'CIPP cannot access this tenant. Perform a CPV Refresh and Access Check via the settings menu' } '*AADSTS50020*' { 'AADSTS50020: The user you have used for your Secure Application Model is a guest in this tenant, or your are using GDAP and have not added the user to the correct group. Please delete the guest user to gain access to this tenant' } diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 index 6bf8bcf280f2..9236b7559fcf 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 @@ -22,20 +22,15 @@ function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $N try { $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType) } catch { - $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error - if ($Message.innerError) { $Message = $Message.Innererror.Message } else { $Message = $Message.Message.Error } - if ($Message -eq $null) { - try { - $Message = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).message - } catch { - $Message = $($_.Exception.Message) - } + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message } throw $Message } return $ReturnedData } else { Write-Error 'Not allowed. You cannot manage your own tenant or tenants not under your scope' - } } \ No newline at end of file diff --git a/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 index 822ed28b42b4..8f9e3d89936b 100644 --- a/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/NinjaOne/Get-NinjaOneOrgMapping.ps1 @@ -24,7 +24,7 @@ function Get-NinjaOneOrgMapping { $After = 0 $PageSize = 1000 $NinjaOrgs = do { - $Result = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organizations?pageSize=$PageSize&after=$After" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $Result = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organizations?pageSize=$PageSize&after=$After" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 $Result | Select-Object name, @{n = 'value'; e = { $_.id } } $ResultCount = ($Result.id | Measure-Object -Maximum) $After = $ResultCount.maximum @@ -32,7 +32,13 @@ function Get-NinjaOneOrgMapping { } while ($ResultCount.count -eq $PageSize) } catch { - $NinjaOrgs = @() + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + + $NinjaOrgs = @(@{ name = $Message }) } $MappingObj = [PSCustomObject]@{ diff --git a/Modules/CippExtensions/NinjaOne/Get-NinjaOneToken.ps1 b/Modules/CippExtensions/NinjaOne/Get-NinjaOneToken.ps1 index 17ebad207a2d..0b1692148531 100644 --- a/Modules/CippExtensions/NinjaOne/Get-NinjaOneToken.ps1 +++ b/Modules/CippExtensions/NinjaOne/Get-NinjaOneToken.ps1 @@ -7,7 +7,7 @@ function Get-NinjaOneToken { if (!$ENV:NinjaClientSecret) { $null = Connect-AzAccount -Identity - $ClientSecret = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name "NinjaOne" -AsPlainText) + $ClientSecret = (Get-AzKeyVaultSecret -VaultName $ENV:WEBSITE_DEPLOYMENT_ID -Name 'NinjaOne' -AsPlainText) } else { $ClientSecret = $ENV:NinjaClientSecret } @@ -20,7 +20,17 @@ function Get-NinjaOneToken { scope = 'monitoring management' } - $token = Invoke-RestMethod -Uri "https://$($Configuration.Instance -replace '/ws','')/ws/oauth/token" -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' + try { + + $token = Invoke-RestMethod -Uri "https://$($Configuration.Instance -replace '/ws','')/ws/oauth/token" -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' + } catch { + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + Write-LogMessage -Message $Message -sev error -API 'NinjaOne' + } return $token } \ No newline at end of file diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 index 903cee63234c..a8363917efcc 100644 --- a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 +++ b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 @@ -5,7 +5,7 @@ function Invoke-NinjaOneDeviceWebhook { $Configuration ) try { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook Recieved - Updating NinjaOne Device compliance for $($Data.resourceData.id) in $($Data.tenantId)" -Sev "Info" -tenant $TenantFilter + Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook Recieved - Updating NinjaOne Device compliance for $($Data.resourceData.id) in $($Data.tenantId)" -Sev 'Info' -tenant $TenantFilter $MappedFields = [pscustomobject]@{} $CIPPMapping = Get-CIPPTable -TableName CippMapping $Filter = "PartitionKey eq 'NinjaFieldMapping'" @@ -36,9 +36,9 @@ function Invoke-NinjaOneDeviceWebhook { "$($MappedFields.DeviceCompliance)" = $Compliant } | ConvertTo-Json - $Null = Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/device/$($Device.NinjaOneID)/custom-fields" -Method PATCH -Body $ComplianceBody -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' + $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device/$($Device.NinjaOneID)/custom-fields" -Method PATCH -Body $ComplianceBody -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' - Write-Host "Updated NinjaOne Device Compliance" + Write-Host 'Updated NinjaOne Device Compliance' } else { @@ -48,8 +48,13 @@ function Invoke-NinjaOneDeviceWebhook { } } catch { - Write-Error "Failed NinjaOne Device Webhook for: $($Data | ConvertTo-Json -depth 100) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $($_.Exception.message)" - Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "Failed NinjaOne Device Webhook Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $($_.Exception.message)" -Sev 'Error' + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + Write-Error "Failed NinjaOne Device Webhook for: $($Data | ConvertTo-Json -Depth 100) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" + Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "Failed NinjaOne Device Webhook Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" -Sev 'Error' } diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index 63e10c8043e9..05b4ecbcaaac 100644 --- a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -19,7 +19,7 @@ function Invoke-NinjaOneTenantSync { $MappedTenant = $QueueItem.MappedTenant # Check for active instances for this tenant - $CurrentItem = $CurrentMap | where-object { $_.RowKey -eq $MappedTenant.RowKey } + $CurrentItem = $CurrentMap | Where-Object { $_.RowKey -eq $MappedTenant.RowKey } $StartDate = try { Get-Date($CurrentItem.lastStartTime) } catch { $Null } $EndDate = try { Get-Date($CurrentItem.lastEndTime) } catch { $Null } @@ -30,10 +30,10 @@ function Invoke-NinjaOneTenantSync { # Set Last Start Time $MappingTable = Get-CIPPTable -TableName CippMapping - $CurrentItem | Add-Member -NotePropertyName lastStartTime -NotePropertyValue ([string]$(($StartQueueTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"))) -Force + $CurrentItem | Add-Member -NotePropertyName lastStartTime -NotePropertyValue ([string]$(($StartQueueTime).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) -Force $CurrentItem | Add-Member -NotePropertyName lastStatus -NotePropertyValue 'Running' -Force if ($Null -ne $CurrentItem.lastEndTime -and $CurrentItem.lastEndTime -ne '' ) { - $CurrentItem.lastEndTime = ([string]$(($CurrentItem.lastEndTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"))) + $CurrentItem.lastEndTime = ([string]$(($CurrentItem.lastEndTime).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) } Add-CIPPAzDataTableEntity @MappingTable -Entity $CurrentItem -Force @@ -44,7 +44,7 @@ function Invoke-NinjaOneTenantSync { $CIPPUrl = ($NinjaSettings | Where-Object { $_.RowKey -eq 'CIPPURL' }).SettingValue - $Customer = Get-Tenants | where-object { $_.customerId -eq $MappedTenant.RowKey } + $Customer = Get-Tenants | Where-Object { $_.customerId -eq $MappedTenant.RowKey } Write-Host "Processing: $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Processing NinjaOne Synchronization for $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" -Sev 'Info' @@ -74,14 +74,14 @@ function Invoke-NinjaOneTenantSync { $After = 0 $PageSize = 1000 $NinjaDevices = do { - $Result = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/devices-detailed?pageSize=$PageSize&after=$After&df=org = $($NinjaOneOrg)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $Result = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/devices-detailed?pageSize=$PageSize&after=$After&df=org = $($NinjaOneOrg)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 $Result $ResultCount = ($Result.id | Measure-Object -Maximum) $After = $ResultCount.maximum } while ($ResultCount.count -eq $PageSize) - Write-Host "Fetched NinjaOne Devices" + Write-Host 'Fetched NinjaOne Devices' [System.Collections.Generic.List[PSCustomObject]]$NinjaOneUserDocs = @() @@ -170,7 +170,7 @@ function Invoke-NinjaOneTenantSync { # Get NinjaOne Users - [System.Collections.Generic.List[PSCustomObject]]$NinjaOneUserDocs = ((Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents?organizationIds=$($NinjaOneOrg)&templateIds=$($NinjaOneUsersTemplate.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100) + [System.Collections.Generic.List[PSCustomObject]]$NinjaOneUserDocs = ((Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents?organizationIds=$($NinjaOneOrg)&templateIds=$($NinjaOneUsersTemplate.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100) foreach ($NinjaDoc in $NinjaOneUserDocs) { $ParsedFields = [pscustomobject]@{} @@ -185,7 +185,7 @@ function Invoke-NinjaOneTenantSync { $NinjaDoc | Add-Member -NotePropertyName 'ParsedFields' -NotePropertyValue $ParsedFields -Force } - Write-Host "Fetched NinjaOne User Docs" + Write-Host 'Fetched NinjaOne User Docs' } [System.Collections.Generic.List[PSCustomObject]]$NinjaOneLicenseDocs = @() @@ -240,7 +240,7 @@ function Invoke-NinjaOneTenantSync { $NinjaOneLicenseTemplate = Invoke-NinjaOneDocumentTemplate -Template $LicenseDocTemplate -Token $Token # Get NinjaOne Licenses - [System.Collections.Generic.List[PSCustomObject]]$NinjaOneLicenseDocs = ((Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents?organizationIds=$($NinjaOneOrg)&templateIds=$($NinjaOneLicenseTemplate.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100) + [System.Collections.Generic.List[PSCustomObject]]$NinjaOneLicenseDocs = ((Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents?organizationIds=$($NinjaOneOrg)&templateIds=$($NinjaOneLicenseTemplate.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100) foreach ($NinjaLic in $NinjaOneLicenseDocs) { $ParsedFields = [pscustomobject]@{} @@ -255,7 +255,7 @@ function Invoke-NinjaOneTenantSync { $NinjaLic | Add-Member -NotePropertyName 'ParsedFields' -NotePropertyValue $ParsedFields -Force } - Write-Host "Fetched NinjaOne License Docs" + Write-Host 'Fetched NinjaOne License Docs' } @@ -334,14 +334,14 @@ function Invoke-NinjaOneTenantSync { ) - write-verbose "$(Get-Date) - Fetching Bulk Data" + Write-Verbose "$(Get-Date) - Fetching Bulk Data" try { $TenantResults = New-GraphBulkRequest -Requests $TenantRequests -tenantid $TenantFilter -NoAuthCheck $True } catch { Throw "Failed to fetch bulk company data: $_" } - Write-Host "Fetched Bulk M365 Data" + Write-Host 'Fetched Bulk M365 Data' $Users = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Users' @@ -376,11 +376,11 @@ function Invoke-NinjaOneTenantSync { $TenantDetails = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'TenantDetails' - write-verbose "$(Get-Date) - Parsing Users" + Write-Verbose "$(Get-Date) - Parsing Users" # Grab licensed users - $licensedUsers = $Users | where-object { $null -ne $_.AssignedLicenses.SkuId } | Sort-Object UserPrincipalName + $licensedUsers = $Users | Where-Object { $null -ne $_.AssignedLicenses.SkuId } | Sort-Object UserPrincipalName - write-verbose "$(Get-Date) - Parsing Roles" + Write-Verbose "$(Get-Date) - Parsing Roles" # Get All Roles $AllRoles = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'AllRoles' @@ -401,13 +401,13 @@ function Invoke-NinjaOneTenantSync { $MemberReturn = $null } - Write-Host "Fetched M365 Roles" + Write-Host 'Fetched M365 Roles' $Roles = foreach ($Result in $MemberReturn) { [PSCustomObject]@{ ID = $Result.id - DisplayName = ($AllRoles | where-object { $_.id -eq $Result.id }).displayName - Description = ($AllRoles | where-object { $_.id -eq $Result.id }).description + DisplayName = ($AllRoles | Where-Object { $_.id -eq $Result.id }).displayName + Description = ($AllRoles | Where-Object { $_.id -eq $Result.id }).description Members = $Result.body.value ParsedMembers = $Result.body.value.Displayname -join ', ' } @@ -415,9 +415,9 @@ function Invoke-NinjaOneTenantSync { - $AdminUsers = (($Roles | Where-Object { $_.Displayname -match "Administrator" }).Members | where-object { $null -ne $_.displayName }) + $AdminUsers = (($Roles | Where-Object { $_.Displayname -match 'Administrator' }).Members | Where-Object { $null -ne $_.displayName }) - write-verbose "$(Get-Date) - Fetching Domains" + Write-Verbose "$(Get-Date) - Fetching Domains" try { $RawDomains = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'RawDomains' } catch { @@ -426,20 +426,20 @@ function Invoke-NinjaOneTenantSync { $customerDomains = ($RawDomains | Where-Object { $_.IsVerified -eq $True }).id -join ', ' | Out-String - write-verbose "$(Get-Date) - Parsing Licenses" + Write-Verbose "$(Get-Date) - Parsing Licenses" # Get Licenses $Licenses = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Licenses' # Get the license overview for the tenant if ($Licenses) { - $LicensesParsed = $Licenses | where-object { $_.PrepaidUnits.Enabled -gt 0 } | Select-Object @{N = 'License Name'; E = { (Get-Culture).TextInfo.ToTitleCase((convert-skuname -skuname $_.SkuPartNumber).Tolower()) } }, @{N = 'Active'; E = { $_.PrepaidUnits.Enabled } }, @{N = 'Consumed'; E = { $_.ConsumedUnits } }, @{N = 'Unused'; E = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } + $LicensesParsed = $Licenses | Where-Object { $_.PrepaidUnits.Enabled -gt 0 } | Select-Object @{N = 'License Name'; E = { (Get-Culture).TextInfo.ToTitleCase((convert-skuname -skuname $_.SkuPartNumber).Tolower()) } }, @{N = 'Active'; E = { $_.PrepaidUnits.Enabled } }, @{N = 'Consumed'; E = { $_.ConsumedUnits } }, @{N = 'Unused'; E = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } } - write-verbose "$(Get-Date) - Parsing Devices" + Write-Verbose "$(Get-Date) - Parsing Devices" # Get all devices from Intune $devices = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Devices' - write-verbose "$(Get-Date) - Parsing Device Compliance Polcies" + Write-Verbose "$(Get-Date) - Parsing Device Compliance Polcies" # Fetch Compliance Policy Status $DeviceCompliancePolicies = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'DeviceCompliancePolicies' @@ -459,17 +459,17 @@ function Invoke-NinjaOneTenantSync { $PolicyReturn = $null } - Write-Host "Fetched M365 Device Compliance" + Write-Host 'Fetched M365 Device Compliance' $DeviceComplianceDetails = foreach ($Result in $PolicyReturn) { [pscustomobject]@{ - ID = ($DeviceCompliancePolicies | where-object { $_.id -eq $Result.id }).id - DisplayName = ($DeviceCompliancePolicies | where-object { $_.id -eq $Result.id }).DisplayName + ID = ($DeviceCompliancePolicies | Where-Object { $_.id -eq $Result.id }).id + DisplayName = ($DeviceCompliancePolicies | Where-Object { $_.id -eq $Result.id }).DisplayName DeviceStatuses = $Result.body.value } } - write-verbose "$(Get-Date) - Parsing Groups" + Write-Verbose "$(Get-Date) - Parsing Groups" # Fetch Groups $AllGroups = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Groups' @@ -489,17 +489,17 @@ function Invoke-NinjaOneTenantSync { $GroupMembersReturn = $null } - Write-Host "Fetched M365 Group Membership" + Write-Host 'Fetched M365 Group Membership' $Groups = foreach ($Result in $GroupMembersReturn) { [pscustomobject]@{ ID = $Result.id - DisplayName = ($AllGroups | where-object { $_.id -eq $Result.id }).DisplayName + DisplayName = ($AllGroups | Where-Object { $_.id -eq $Result.id }).DisplayName Members = $result.body.value } } - write-verbose "$(Get-Date) - Parsing Conditional Access Polcies" + Write-Verbose "$(Get-Date) - Parsing Conditional Access Polcies" # Fetch and parse conditional access polcies $AllConditionalAccessPolcies = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'ConditionalAccess' @@ -509,43 +509,43 @@ function Invoke-NinjaOneTenantSync { # Check for All Include if ($CAPolicy.conditions.users.includeUsers -contains 'All') { - $Users | foreach-object { $null = $CAMembers.add($_.id) } + $Users | ForEach-Object { $null = $CAMembers.add($_.id) } } else { # Add any specific all users to the array - $CAPolicy.conditions.users.includeUsers | foreach-object { $null = $CAMembers.add($_) } + $CAPolicy.conditions.users.includeUsers | ForEach-Object { $null = $CAMembers.add($_) } } # Now all members of groups foreach ($CAIGroup in $CAPolicy.conditions.users.includeGroups) { - foreach ($Member in ($Groups | where-object { $_.id -eq $CAIGroup }).Members) { + foreach ($Member in ($Groups | Where-Object { $_.id -eq $CAIGroup }).Members) { $null = $CAMembers.add($Member.id) } } # Now all members of roles foreach ($CAIRole in $CAPolicy.conditions.users.includeRoles) { - foreach ($Member in ($Roles | where-object { $_.id -eq $CAIRole }).Members) { + foreach ($Member in ($Roles | Where-Object { $_.id -eq $CAIRole }).Members) { $null = $CAMembers.add($Member.id) } } # Parse to Unique members - $CAMembers = $CAMembers | select-object -unique + $CAMembers = $CAMembers | Select-Object -Unique if ($CAMembers) { # Now remove excluded users - $CAPolicy.conditions.users.excludeUsers | foreach-object { $null = $CAMembers.remove($_) } + $CAPolicy.conditions.users.excludeUsers | ForEach-Object { $null = $CAMembers.remove($_) } # Excluded Groups foreach ($CAEGroup in $CAPolicy.conditions.users.excludeGroups) { - foreach ($Member in ($Groups | where-object { $_.id -eq $CAEGroup }).Members) { + foreach ($Member in ($Groups | Where-Object { $_.id -eq $CAEGroup }).Members) { $null = $CAMembers.remove($Member.id) } } # Excluded Roles foreach ($CAIRole in $CAPolicy.conditions.users.excludeRoles) { - foreach ($Member in ($Roles | where-object { $_.id -eq $CAERole }).Members) { + foreach ($Member in ($Roles | Where-Object { $_.id -eq $CAERole }).Members) { $null = $CAMembers.remove($Member.id) } } @@ -558,15 +558,15 @@ function Invoke-NinjaOneTenantSync { } } - write-verbose "$(Get-Date) - Fetching One Drive Details" + Write-Verbose "$(Get-Date) - Fetching One Drive Details" try { - $OneDriveDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getOneDriveUsageAccountDetail(period='D7')" -tenantid $TenantFilter | convertfrom-csv + $OneDriveDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getOneDriveUsageAccountDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv } catch { Write-Error "Failed to fetch Onedrive Details: $_" $OneDriveDetails = $null } - write-verbose "$(Get-Date) - Fetching CAS Mailbox Details" + Write-Verbose "$(Get-Date) - Fetching CAS Mailbox Details" try { $CASFull = New-GraphGetRequest -uri "https://outlook.office365.com/adminapi/beta/$($tenantfilter)/CasMailbox" -Tenantid $Customer.defaultDomainName -scope ExchangeOnline -noPagination $true } catch { @@ -574,7 +574,7 @@ function Invoke-NinjaOneTenantSync { $CASFull = $null } - write-verbose "$(Get-Date) - Fetching Mailbox Details" + Write-Verbose "$(Get-Date) - Fetching Mailbox Details" try { $MailboxDetailedFull = New-ExoRequest -TenantID $Customer.defaultDomainName -cmdlet 'Get-Mailbox' } catch { @@ -582,7 +582,7 @@ function Invoke-NinjaOneTenantSync { $MailboxDetailedFull = $null } - write-verbose "$(Get-Date) - Fetching Blocked Mailbox Details" + Write-Verbose "$(Get-Date) - Fetching Blocked Mailbox Details" try { $BlockedSenders = New-ExoRequest -TenantID $Customer.defaultDomainName -cmdlet 'Get-BlockedSenderAddress' } catch { @@ -590,15 +590,15 @@ function Invoke-NinjaOneTenantSync { $BlockedSenders = $null } - write-verbose "$(Get-Date) - Fetching Mailbox Stats" + Write-Verbose "$(Get-Date) - Fetching Mailbox Stats" try { - $MailboxStatsFull = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D7')" -tenantid $TenantFilter | convertfrom-csv + $MailboxStatsFull = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D7')" -tenantid $TenantFilter | ConvertFrom-Csv } catch { Write-Error "Failed to fetch Mailbox Stats: $_" $MailboxStatsFull = $null } - Write-Host "Fetched M365 Additional Data" + Write-Host 'Fetched M365 Additional Data' $FetchEnd = Get-Date @@ -671,7 +671,7 @@ function Invoke-NinjaOneTenantSync { id = $FoundUser.Id name = $FoundUser.displayName upn = $FoundUser.userPrincipalName - lastlogin = ($DeviceUser.lastLogOnDateTime).ToString("yyyy-MM-dd") + lastlogin = ($DeviceUser.lastLogOnDateTime).ToString('yyyy-MM-dd') } ) } @@ -741,7 +741,7 @@ function Invoke-NinjaOneTenantSync { Add-CIPPAzDataTableEntity @DeviceTable -Entity @{ PartitionKey = $Customer.CustomerId RowKey = $device.AzureADDeviceId - RawDevice = "$($ParsedDevice | ConvertTo-Json -Depth 100 -compress)" + RawDevice = "$($ParsedDevice | ConvertTo-Json -Depth 100 -Compress)" } $ParsedDevices.add($ParsedDevice) @@ -770,7 +770,7 @@ function Invoke-NinjaOneTenantSync { - $DeviceLinksHTML = Get-NinjaOneLinks -Data $DeviceLinksData -SmallCols 2 -MedCols 3 -LargeCols 3 -XLCols 3 + $DeviceLinksHTML = Get-NinjaOneLinks -Data $DeviceLinksData -SmallCols 2 -MedCols 3 -LargeCols 3 -XLCols 3 $DeviceLinksHtml = '
' + $DeviceLinksHTML + '
' @@ -800,7 +800,7 @@ function Invoke-NinjaOneTenantSync { 'Management Type' = $Device.managementAgent } - $DeviceDetailsCard = Get-NinjaOneInfoCard -Title "Device Details" -Data $DeviceDetailsData -Icon 'fas fa-laptop' + $DeviceDetailsCard = Get-NinjaOneInfoCard -Title 'Device Details' -Data $DeviceDetailsData -Icon 'fas fa-laptop' # Device Hardware $DeviceHardwareData = [PSCustomObject]@{ @@ -812,7 +812,7 @@ function Invoke-NinjaOneTenantSync { 'Manufacturer' = $Device.manufacturer } - $DeviceHardwareCard = Get-NinjaOneInfoCard -Title "Device Details" -Data $DeviceHardwareData -Icon 'fas fa-microchip' + $DeviceHardwareCard = Get-NinjaOneInfoCard -Title 'Device Details' -Data $DeviceHardwareData -Icon 'fas fa-microchip' # Device Enrollment $DeviceEnrollmentData = [PSCustomObject]@{ @@ -825,7 +825,7 @@ function Invoke-NinjaOneTenantSync { 'Credential Guard' = $Device.hardwareinformation.deviceGuardLocalSystemAuthorityCredentialGuardState } - $DeviceEnrollmentCard = Get-NinjaOneInfoCard -Title "Device Enrollment" -Data $DeviceEnrollmentData -Icon 'fas fa-table-list' + $DeviceEnrollmentCard = Get-NinjaOneInfoCard -Title 'Device Enrollment' -Data $DeviceEnrollmentData -Icon 'fas fa-table-list' # Compliance Policies @@ -870,7 +870,7 @@ function Invoke-NinjaOneTenantSync { # Update Device if ($MappedFields.DeviceSummary -or $MappedFields.DeviceLinks -or $MappedFields.DeviceCompliance) { - $Result = Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/device/$($MatchedNinjaDevice.id)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaDeviceUpdate | ConvertTo-Json -Depth 100) + $Result = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device/$($MatchedNinjaDevice.id)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaDeviceUpdate | ConvertTo-Json -Depth 100) } } @@ -879,7 +879,7 @@ function Invoke-NinjaOneTenantSync { New-CIPPGraphSubscription -TenantFilter $TenantFilter -TypeofSubscription 'updated' -BaseURL $CIPPUrl -Resource 'devices' -EventType 'DeviceUpdate' -ExecutingUser 'NinjaOneSync' } - Write-Host "Processed Devices" + Write-Host 'Processed Devices' ########## Create / Update User Objects @@ -923,12 +923,12 @@ function Invoke-NinjaOneTenantSync { } - foreach ($user in $SyncUsers | where-object { $_.id -notin $ParsedUsers.RowKey }) { + foreach ($user in $SyncUsers | Where-Object { $_.id -notin $ParsedUsers.RowKey }) { try { $NinjaOneUser = $NinjaOneUserDocs | Where-Object { $_.ParsedFields.cippUserID -eq $User.ID } - if (($NinjaOneUser | Measure-Object).count -gt 1) { - Throw "Multiple Users with the same ID found" + if (($NinjaOneUser | Measure-Object).count -gt 1) { + Throw 'Multiple Users with the same ID found' } @@ -1005,16 +1005,16 @@ function Invoke-NinjaOneTenantSync { } - $UserDevicesDetailsRaw = $ParsedDevices | where-object { $User.id -in $_.UserIDS } + $UserDevicesDetailsRaw = $ParsedDevices | Where-Object { $User.id -in $_.UserIDS } - $UserDevices = foreach ($UserDevice in $ParsedDevices | where-object { $User.id -in $_.UserIDS }) { + $UserDevices = foreach ($UserDevice in $ParsedDevices | Where-Object { $User.id -in $_.UserIDS }) { $MatchedNinjaDevice = $UserDevice.NinjaDevice $ParsedDeviceName = $UserDevice.DeviceLink # Set Last Login Time - $LastLoginTime = ($UserDevice.UserDetails | where-object { $_.id -eq $User.id }).lastLogin + $LastLoginTime = ($UserDevice.UserDetails | Where-Object { $_.id -eq $User.id }).lastLogin if (!$LastLoginTime) { $LastLoginTime = 'Unknown' } @@ -1052,7 +1052,7 @@ function Invoke-NinjaOneTenantSync { - $UserOneDriveStats = $OneDriveDetails | where-object { $_.'Owner Principal Name' -eq $User.userPrincipalName } | Select-Object -First 1 + $UserOneDriveStats = $OneDriveDetails | Where-Object { $_.'Owner Principal Name' -eq $User.userPrincipalName } | Select-Object -First 1 $UserOneDriveUse = $UserOneDriveStats.'Storage Used (Byte)' / 1GB $UserOneDriveTotal = $UserOneDriveStats.'Storage Allocated (Byte)' / 1GB @@ -1105,7 +1105,7 @@ function Invoke-NinjaOneTenantSync { } - $UserMailboxStats = $MailboxStatsFull | where-object { $_.'User Principal Name' -eq $User.userPrincipalName } | Select-Object -First 1 + $UserMailboxStats = $MailboxStatsFull | Where-Object { $_.'User Principal Name' -eq $User.userPrincipalName } | Select-Object -First 1 $UserMailUse = $UserMailboxStats.'Storage Used (Byte)' / 1GB $UserMailTotal = $UserMailboxStats.'Prohibit Send/Receive Quota (Byte)' / 1GB @@ -1278,11 +1278,11 @@ function Invoke-NinjaOneTenantSync { # UsersSummaryCards: - $UserOverviewCardHTML = Get-NinjaOneInfoCard -Title "User Details" -Data $UserOverviewCard -Icon 'fas fa-user' - $MailboxDetailsCardHTML = Get-NinjaOneInfoCard -Title "Mailbox Details" -Data $MailboxDetailsCardData -Icon 'fas fa-envelope' - $MailboxSettingsCardHTML = Get-NinjaOneInfoCard -Title "Mailbox Settings" -Data $MailboxSettingsCard -Icon 'fas fa-envelope' - $OneDriveCardHTML = Get-NinjaOneInfoCard -Title "OneDrive Details" -Data $OneDriveCardData -Icon 'fas fa-envelope' - $UserPolciesCard = Get-NinjaOneCard -Title "Assigned Conditional Access Policies" -Body $UserPoliciesFormatted + $UserOverviewCardHTML = Get-NinjaOneInfoCard -Title 'User Details' -Data $UserOverviewCard -Icon 'fas fa-user' + $MailboxDetailsCardHTML = Get-NinjaOneInfoCard -Title 'Mailbox Details' -Data $MailboxDetailsCardData -Icon 'fas fa-envelope' + $MailboxSettingsCardHTML = Get-NinjaOneInfoCard -Title 'Mailbox Settings' -Data $MailboxSettingsCard -Icon 'fas fa-envelope' + $OneDriveCardHTML = Get-NinjaOneInfoCard -Title 'OneDrive Details' -Data $OneDriveCardData -Icon 'fas fa-envelope' + $UserPolciesCard = Get-NinjaOneCard -Title 'Assigned Conditional Access Policies' -Body $UserPoliciesFormatted $UserSummaryHTML = '
' + @@ -1311,7 +1311,7 @@ function Invoke-NinjaOneTenantSync { $UserFields = @{ cippUserLinks = @{'html' = $UserLinksHTML } cippUserSummary = @{'html' = $UserSummaryHTML } - cippUserGroups = @{'html' = "$($UserGroups | ConvertTo-HTML -As Table -Fragment)" } + cippUserGroups = @{'html' = "$($UserGroups | ConvertTo-Html -As Table -Fragment)" } cippUserDevices = @{'html' = $UserDeviceDetailHTML } cippUserID = $User.id cippUserUPN = $User.userPrincipalName @@ -1355,8 +1355,8 @@ function Invoke-NinjaOneTenantSync { try { # Create New Users if (($NinjaUserCreation | Measure-Object).count -ge 100) { - Write-Host "Creating NinjaOne Users" - [System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 + Write-Host 'Creating NinjaOne Users' + [System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserCreation [System.Collections.Generic.List[PSCustomObject]]$NinjaUserCreation = @() } @@ -1367,8 +1367,8 @@ function Invoke-NinjaOneTenantSync { try { # Update Users if (($NinjaUserUpdates | Measure-Object).count -ge 100) { - Write-Host "Updating NinjaOne Users" - [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 + Write-Host 'Updating NinjaOne Users' + [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserUpdates [System.Collections.Generic.List[PSCustomObject]]$NinjaUserUpdates = @() } @@ -1406,7 +1406,7 @@ function Invoke-NinjaOneTenantSync { Add-CIPPAzDataTableEntity @UsersMapTable -Entity $MappedUser -Force } } else { - Write-Error "Unmatched Doc: $($UserDoc | convertto-json -depth 100)" + Write-Error "Unmatched Doc: $($UserDoc | ConvertTo-Json -Depth 100)" } } @@ -1430,8 +1430,8 @@ function Invoke-NinjaOneTenantSync { try { # Create New Users if (($NinjaUserCreation | Measure-Object).count -ge 1) { - Write-Host "Creating NinjaOne Users" - [System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 + Write-Host 'Creating NinjaOne Users' + [System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserCreation } @@ -1442,8 +1442,8 @@ function Invoke-NinjaOneTenantSync { try { # Update Users if (($NinjaUserUpdates | Measure-Object).count -ge 1) { - Write-Host "Updating NinjaOne Users" - [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 + Write-Host 'Updating NinjaOne Users' + [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserUpdates } } Catch { @@ -1483,7 +1483,7 @@ function Invoke-NinjaOneTenantSync { Add-CIPPAzDataTableEntity @UsersMapTable -Entity $MappedUser -Force } } else { - Write-Error "Unmatched Doc: $($UserDoc | convertto-json -depth 100)" + Write-Error "Unmatched Doc: $($UserDoc | ConvertTo-Json -Depth 100)" } } @@ -1492,7 +1492,7 @@ function Invoke-NinjaOneTenantSync { # Relate Users to Devices Foreach ($LinkDevice in $ParsedDevices | Where-Object { $null -ne $_.NinjaDevice }) { - $RelatedItems = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/related-items/with-entity/NODE/$($LinkDevice.NinjaDevice.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $RelatedItems = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/with-entity/NODE/$($LinkDevice.NinjaDevice.id)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 [System.Collections.Generic.List[PSCustomObject]]$Relations = @() Foreach ($LinkUser in $LinkDevice.UserIDs) { $MatchedUser = $UsersMap | Where-Object { $_.M365ID -eq $LinkUser } @@ -1501,7 +1501,7 @@ function Invoke-NinjaOneTenantSync { if (!$ExistingRelation) { $Relations.Add( [PSCustomObject]@{ - relEntityType = "DOCUMENT" + relEntityType = 'DOCUMENT' relEntityId = $MatchedUser.NinjaOneID } ) @@ -1514,9 +1514,9 @@ function Invoke-NinjaOneTenantSync { try { # Update Relations if (($Relations | Measure-Object).count -ge 1) { - Write-Host "Updating Relations" - $Null = Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/related-items/entity/NODE/$($LinkDevice.NinjaDevice.id)/relations" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' -Body ($Relations | ConvertTo-Json -Depth 100 -AsArray) -EA Stop - Write-Host "Completed Update" + Write-Host 'Updating Relations' + $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/entity/NODE/$($LinkDevice.NinjaDevice.id)/relations" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' -Body ($Relations | ConvertTo-Json -Depth 100 -AsArray) -EA Stop + Write-Host 'Completed Update' } } Catch { Write-Host "Creating Relations Failed: $_" @@ -1565,7 +1565,7 @@ function Invoke-NinjaOneTenantSync { 'Tenant Total' = $License.prepaidUnits.enabled 'SKU ID' = $License.skuId } - $LicenseOverviewCardHTML = Get-NinjaOneInfoCard -Title "License Details" -Data $LicenseSummary -Icon 'fas fa-file-invoice' + $LicenseOverviewCardHTML = Get-NinjaOneInfoCard -Title 'License Details' -Data $LicenseSummary -Icon 'fas fa-file-invoice' $SubscriptionsHTML = $MatchedSubscriptions | Select-Object @{'n' = 'Subscription Licenses'; 'e' = { $_.totalLicenses } }, @{'n' = 'Created'; 'e' = { $_.createdDateTime } }, @@ -1574,7 +1574,7 @@ function Invoke-NinjaOneTenantSync { @{'n' = 'Status'; 'e' = { $_.Status } } | ConvertTo-Html -As Table -Fragment $SubscriptionsHTML = ([System.Web.HttpUtility]::HtmlDecode($SubscriptionsHTML) -replace '', '') -replace '', '' - $SubscriptionCardHTML = Get-NinjaOneCard -Title "Subscriptions" -Body $SubscriptionsHTML -Icon 'fas fa-file-invoice' + $SubscriptionCardHTML = Get-NinjaOneCard -Title 'Subscriptions' -Body $SubscriptionsHTML -Icon 'fas fa-file-invoice' $LicenseItemsTable = $License.servicePlans | Select-Object @{n = 'Plan Name'; e = { convert-skuname -skuname $_.servicePlanName } }, @{n = 'Applies To'; e = { $_.appliesTo } }, @{n = 'Provisioning Status'; e = { $_.provisioningStatus } } @@ -1626,8 +1626,8 @@ function Invoke-NinjaOneTenantSync { try { # Create New Subscriptions if (($NinjaLicenseCreation | Measure-Object).count -ge 1) { - Write-Host "Creating NinjaOne Licenses" - [System.Collections.Generic.List[PSCustomObject]]$CreatedLicenses = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaLicenseCreation | ConvertTo-Json -Depth 100 -AsArray) -EA Stop).content | ConvertFrom-Json -Depth 100 + Write-Host 'Creating NinjaOne Licenses' + [System.Collections.Generic.List[PSCustomObject]]$CreatedLicenses = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaLicenseCreation | ConvertTo-Json -Depth 100 -AsArray) -EA Stop).content | ConvertFrom-Json -Depth 100 } } Catch { Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" @@ -1636,9 +1636,9 @@ function Invoke-NinjaOneTenantSync { try { # Update Subscriptions if (($NinjaLicenseUpdates | Measure-Object).count -ge 1) { - Write-Host "Updating NinjaOne Licenses" - [System.Collections.Generic.List[PSCustomObject]]$UpdatedLicenses = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaLicenseUpdates | ConvertTo-Json -Depth 100 -AsArray) -EA Stop).content | ConvertFrom-Json -Depth 100 - Write-Host "Completed Update" + Write-Host 'Updating NinjaOne Licenses' + [System.Collections.Generic.List[PSCustomObject]]$UpdatedLicenses = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaLicenseUpdates | ConvertTo-Json -Depth 100 -AsArray) -EA Stop).content | ConvertFrom-Json -Depth 100 + Write-Host 'Completed Update' } } Catch { Write-Host "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" @@ -1652,14 +1652,14 @@ function Invoke-NinjaOneTenantSync { $MatchedLicDoc = $LicenseDocs | Where-Object { $_.documentName -eq $LinkLic.name } if (($MatchedLicDoc | Measure-Object).count -eq 1) { # Remove existing relations - $RelatedItems = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/related-items/with-entity/DOCUMENT/$($MatchedLicDoc.documentId)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $RelatedItems = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/with-entity/DOCUMENT/$($MatchedLicDoc.documentId)" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 [System.Collections.Generic.List[PSCustomObject]]$Relations = @() Foreach ($LinkUser in $LinkLic.Users) { $ExistingRelation = $RelatedItems | Where-Object { $_.relEntityType -eq 'DOCUMENT' -and $_.relEntityId -eq $LinkUser } if (!$ExistingRelation) { $Relations.Add( [PSCustomObject]@{ - relEntityType = "DOCUMENT" + relEntityType = 'DOCUMENT' relEntityId = $LinkUser } ) @@ -1670,9 +1670,9 @@ function Invoke-NinjaOneTenantSync { try { # Update Relations if (($Relations | Measure-Object).count -ge 1) { - Write-Host "Updating Relations" - $Null = Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/related-items/entity/DOCUMENT/$($($MatchedLicDoc.documentId))/relations" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' -Body ($Relations | ConvertTo-Json -Depth 100 -AsArray) -EA Stop - Write-Host "Completed Update" + Write-Host 'Updating Relations' + $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/entity/DOCUMENT/$($($MatchedLicDoc.documentId))/relations" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' -Body ($Relations | ConvertTo-Json -Depth 100 -AsArray) -EA Stop + Write-Host 'Completed Update' } } Catch { Write-Host "Creating Relations Failed: $_" @@ -1681,7 +1681,7 @@ function Invoke-NinjaOneTenantSync { #Remove relations foreach ($DelUser in $RelatedItems | Where-Object { $_.relEntityType -eq 'DOCUMENT' -and $_.relEntityId -notin $LinkLic.Users }) { try { - $RelatedItems = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/related-items/$($DelUser.id)" -Method Delete -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $RelatedItems = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/$($DelUser.id)" -Method Delete -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 } catch { Write-Host "Failed to remove relation $($DelUser.id) from $($LinkLic.name)" } @@ -1698,7 +1698,7 @@ function Invoke-NinjaOneTenantSync { ### M365 Links Section if ($MappedFields.TenantLinks) { - Write-Host "Tenant Links" + Write-Host 'Tenant Links' $ManagementLinksData = @( @{ @@ -1800,12 +1800,12 @@ function Invoke-NinjaOneTenantSync { if ($MappedFields.TenantSummary) { - Write-Host "Tenant Summary" + Write-Host 'Tenant Summary' ### Tenant Overview Card $ParsedAdmins = [PSCustomObject]@{} - $AdminUsers | Select-Object displayname, userPrincipalName -unique | ForEach-Object { + $AdminUsers | Select-Object displayname, userPrincipalName -Unique | ForEach-Object { $ParsedAdmins | Add-Member -NotePropertyName $_.displayname -NotePropertyValue $_.userPrincipalName } @@ -1819,15 +1819,15 @@ function Invoke-NinjaOneTenantSync { } - $TenantSummaryCard = Get-NinjaOneInfoCard -Title "Tenant Details" -Data $TenantDetailsItems -Icon 'fas fa-building' + $TenantSummaryCard = Get-NinjaOneInfoCard -Title 'Tenant Details' -Data $TenantDetailsItems -Icon 'fas fa-building' ### Users details card - Write-Host "User Details" - $TotalUsersCount = ($Users | measure-object).count - $GuestUsersCount = ($Users | where-object { $_.UserType -eq 'Guest' } | measure-object).count - $LicensedUsersCount = ($licensedUsers | measure-object).count + Write-Host 'User Details' + $TotalUsersCount = ($Users | Measure-Object).count + $GuestUsersCount = ($Users | Where-Object { $_.UserType -eq 'Guest' } | Measure-Object).count + $LicensedUsersCount = ($licensedUsers | Measure-Object).count $UnlicensedUsersCount = $TotalUsersCount - $GuestUsersCount - $LicensedUsersCount - $UsersEnabledCount = ($Users | where-object { $_.accountEnabled -eq $True } | Measure-Object).count + $UsersEnabledCount = ($Users | Where-Object { $_.accountEnabled -eq $True } | Measure-Object).count # Enabled Users @@ -1845,7 +1845,7 @@ function Invoke-NinjaOneTenantSync { ) - $UsersEnabledChartHTML = Get-NinjaInLineBarGraph -Title "User Status" -Data $Data -KeyInLine + $UsersEnabledChartHTML = Get-NinjaInLineBarGraph -Title 'User Status' -Data $Data -KeyInLine # User Types @@ -1867,7 +1867,7 @@ function Invoke-NinjaOneTenantSync { } ) - $UsersTypesChartHTML = Get-NinjaInLineBarGraph -Title "User Types" -Data $Data -KeyInLine + $UsersTypesChartHTML = Get-NinjaInLineBarGraph -Title 'User Types' -Data $Data -KeyInLine # Create the Users Card @@ -1880,7 +1880,7 @@ function Invoke-NinjaOneTenantSync { ### Device Details Card - Write-Host "Device Details" + Write-Host 'Device Details' $TotalDeviceswCount = ($Devices | Measure-Object).count $ComplianceDevicesCount = ($Devices | Where-Object { $_.complianceState -eq 'compliant' } | Measure-Object).count $WindowsCount = ($Devices | Where-Object { $_.operatingSystem -eq 'Windows' } | Measure-Object).count @@ -1905,7 +1905,7 @@ function Invoke-NinjaOneTenantSync { ) - $DeviceComplianceChartHTML = Get-NinjaInLineBarGraph -Title "Device Compliance" -Data $Data -KeyInLine + $DeviceComplianceChartHTML = Get-NinjaInLineBarGraph -Title 'Device Compliance' -Data $Data -KeyInLine # Device OS Types @@ -1932,7 +1932,7 @@ function Invoke-NinjaOneTenantSync { } ) - $DeviceOsChartHTML = Get-NinjaInLineBarGraph -Title "Device Operating Systems" -Data $Data -KeyInLine + $DeviceOsChartHTML = Get-NinjaInLineBarGraph -Title 'Device Operating Systems' -Data $Data -KeyInLine # Last online time @@ -1949,7 +1949,7 @@ function Invoke-NinjaOneTenantSync { } ) - $DeviceOnlineChartHTML = Get-NinjaInLineBarGraph -Title "Devices Online in the last 30 days" -Data $Data -KeyInLine + $DeviceOnlineChartHTML = Get-NinjaInLineBarGraph -Title 'Devices Online in the last 30 days' -Data $Data -KeyInLine # Create the Devices Card @@ -1960,7 +1960,7 @@ function Invoke-NinjaOneTenantSync { $DeviceSummaryCardHTML = Get-NinjaOneCard -Title 'Device Details' -Body $DeviceCardBodyHTML -Icon 'fas fa-network-wired' -TitleLink $TitleLink #### Secure Score Card - Write-Host "Secure Score Details" + Write-Host 'Secure Score Details' $Top5Actions = ($SecureScoreParsed | Where-Object { $_.scoreInPercentage -ne 100 } | Sort-Object 'Score Impact', adjustedRank -Descending) | Select-Object -First 5 # Score Chart @@ -1991,7 +1991,7 @@ function Invoke-NinjaOneTenantSync { ### CIPP Applied Standards Cards - Write-Host "Applied Standards" + Write-Host 'Applied Standards' Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.FullName $StandardsDefinitions = Get-Content 'config/standards.json' | ConvertFrom-Json -Depth 100 @@ -2005,10 +2005,10 @@ function Invoke-NinjaOneTenantSync { $ParsedStandards = foreach ($Standard in $AppliedStandards) { [PSCustomObject]$Standards = $Standard.Standards - $Standards.PSObject.Properties | foreach-object { + $Standards.PSObject.Properties | ForEach-Object { $CheckValue = $_ if ($CheckValue.value) { - $MatchedStandard = $StandardsDefinitions | where-object { ($_.name -split 'standards.')[1] -eq $CheckValue.name } + $MatchedStandard = $StandardsDefinitions | Where-Object { ($_.name -split 'standards.')[1] -eq $CheckValue.name } if (($MatchedStandard | Measure-Object).count -eq 1) { '
  • ' + $($MatchedStandard.label) + ' (' + ($($Standard.Tenant)) + ')
  • ' } @@ -2024,8 +2024,8 @@ function Invoke-NinjaOneTenantSync { $CIPPStandardsSummaryCardHTML = Get-NinjaOneCard -Title 'CIPP Applied Standards' -Body $CIPPStandardsBodyHTML -Icon 'fas fa-shield-halved' -TitleLink $TitleLink ### License Card - Write-Host "License Details" - $LicenseTableHTML = $LicensesParsed | Sort-Object 'License Name' | ConvertTo-HTML -As Table -Fragment + Write-Host 'License Details' + $LicenseTableHTML = $LicensesParsed | Sort-Object 'License Name' | ConvertTo-Html -As Table -Fragment $LicenseTableHTML = '
    ' + (([System.Web.HttpUtility]::HtmlDecode($LicenseTableHTML) -replace '', '') -replace '', '') + '
    ' $TitleLink = "https://$CIPPUrl/tenant/administration/list-licenses?customerId=$($Customer.customerId)" @@ -2033,7 +2033,7 @@ function Invoke-NinjaOneTenantSync { ### Summary Stats - Write-Host "Widget Details" + Write-Host 'Widget Details' [System.Collections.Generic.List[PSCustomObject]]$WidgetData = @() @@ -2062,7 +2062,7 @@ function Invoke-NinjaOneTenantSync { # Unused Licenses $WidgetData.add([PSCustomObject]@{ Value = $( - $BPAUnusedLicenses = (($BpaData.Unusedlicenses | ConvertFrom-Json).availableUnits | Measure-Object -sum).sum + $BPAUnusedLicenses = (($BpaData.Unusedlicenses | ConvertFrom-Json).availableUnits | Measure-Object -Sum).sum if ($BPAUnusedLicenses -ne 0) { $ResultColour = '#D53948' } else { @@ -2219,7 +2219,7 @@ function Invoke-NinjaOneTenantSync { # Create the Tenant Summary Field - Write-Host "Complete Tenant Summary" + Write-Host 'Complete Tenant Summary' $TenantSummaryHTML = '
    ' + $SummaryDetailsCardHTML + '
    ' + '
    ' + '
    ' + $TenantSummaryCard + @@ -2237,9 +2237,9 @@ function Invoke-NinjaOneTenantSync { } if ($MappedFields.UsersSummary) { - Write-Host "User Details Section" + Write-Host 'User Details Section' - $UsersTableFornatted = $ParsedUsers | sort-object name | Select-Object -First 100 Name, + $UsersTableFornatted = $ParsedUsers | Sort-Object name | Select-Object -First 100 Name, @{n = 'User Principal Name'; e = { $_.UPN } }, #Aliases, Licenses, @@ -2249,7 +2249,7 @@ function Invoke-NinjaOneTenantSync { Actions - $UsersTableHTML = $UsersTableFornatted | ConvertTo-HTML -As Table -Fragment + $UsersTableHTML = $UsersTableFornatted | ConvertTo-Html -As Table -Fragment $UsersTableHTML = ([System.Web.HttpUtility]::HtmlDecode($UsersTableHTML) -replace '', '') -replace '', '' @@ -2275,20 +2275,20 @@ function Invoke-NinjaOneTenantSync { - Write-Host "Posting Details" + Write-Host 'Posting Details' $Token = Get-NinjaOneToken -configuration $Configuration Write-Host "Ninja Body: $($NinjaOrgUpdate | ConvertTo-Json -Depth 100)" - $Result = Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organization/$($MappedTenant.NinjaOne)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaOrgUpdate | ConvertTo-Json -Depth 100) + $Result = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/$($MappedTenant.NinjaOne)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaOrgUpdate | ConvertTo-Json -Depth 100) - Write-Host "Cleaning Users Cache" + Write-Host 'Cleaning Users Cache' if (($ParsedUsers | Measure-Object).count -gt 0) { Remove-AzDataTableEntity @UsersTable -Entity ($ParsedUsers | Select-Object PartitionKey, RowKey) } - Write-Host "Cleaning Device Cache" + Write-Host 'Cleaning Device Cache' if (($ParsedDevices | Measure-Object).count -gt 0) { Remove-AzDataTableEntity @DeviceTable -Entity ($ParsedDevices | Select-Object PartitionKey, RowKey) } @@ -2297,16 +2297,20 @@ function Invoke-NinjaOneTenantSync { Write-Host "Completed Total Time: $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds)" # Set Last End Time - $CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"))) -Force + $CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) -Force $CurrentItem | Add-Member -NotePropertyName lastStatus -NotePropertyValue 'Completed' -Force Add-CIPPAzDataTableEntity @MappingTable -Entity $CurrentItem -Force Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Completed NinjaOne Sync for $($Customer.displayName). Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds) seconds. Data fetched in $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds) seconds. Total processing time $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds) seconds" -Sev 'info' } catch { - Write-Error "Failed NinjaOne Processing for $($Customer.displayName) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $($_.Exception.message)" - Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Failed NinjaOne Processing for $($Customer.displayName) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $($_.Exception.message)" -Sev 'Error' - $CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"))) -Force + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } Write-Error "Failed NinjaOne Processing for $($Customer.displayName) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" + Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Failed NinjaOne Processing for $($Customer.displayName) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" -Sev 'Error' + $CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) -Force $CurrentItem | Add-Member -NotePropertyName lastStatus -NotePropertyValue 'Failed' -Force Add-CIPPAzDataTableEntity @MappingTable -Entity $CurrentItem -Force } diff --git a/Modules/CippExtensions/Private/Get-HaloMapping.ps1 b/Modules/CippExtensions/Private/Get-HaloMapping.ps1 index adfdd8e72102..ad4fc1e88111 100644 --- a/Modules/CippExtensions/Private/Get-HaloMapping.ps1 +++ b/Modules/CippExtensions/Private/Get-HaloMapping.ps1 @@ -25,8 +25,14 @@ function Get-HaloMapping { $pagecount = [Math]::Ceiling($Result.record_count / 999) } while ($i -le $pagecount) } catch { - Write-LogMessage -Message "Could not get HaloPSA Clients, error: $($_.Exception.Message)" -Level Error -tenant 'CIPP' -API 'HaloMapping' - $RawHaloClients = @(@{name = "Could not get HaloPSA Clients, error: $($_.Exception.Message)" }) + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + + Write-LogMessage -Message "Could not get HaloPSA Clients, error: $Message " -Level Error -tenant 'CIPP' -API 'HaloMapping' + $RawHaloClients = @(@{name = "Could not get HaloPSA Clients, error: $Message" }) } $HaloClients = $RawHaloClients | ForEach-Object { [PSCustomObject]@{ diff --git a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 b/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 index 35b754fa6dfd..5e14f3e1e80d 100644 --- a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 +++ b/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 @@ -39,8 +39,13 @@ function New-HaloPSATicket { try { $Ticket = Invoke-RestMethod -Uri "$($Configuration.ResourceURL)/Tickets" -ContentType 'application/json; charset=utf-8' -Method Post -Body $body -Headers @{Authorization = "Bearer $($token.access_token)" } } catch { - Write-LogMessage -message "Failed to send ticket to HaloPSA: $($_.Exception.Message)" -API 'HaloPSATicket' -sev Error - Write-Host "Failed to send ticket to HaloPSA: $($_.Exception.Message)" + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + Write-LogMessage -message "Failed to send ticket to HaloPSA: $Message" -API 'HaloPSATicket' -sev Error + Write-Host "Failed to send ticket to HaloPSA: $Message" } } \ No newline at end of file diff --git a/version_latest.txt b/version_latest.txt index 26611488b0a0..553fe87a05ea 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.0.3 \ No newline at end of file +5.0.4 \ No newline at end of file