From 98a9474e119ce08d04cb0fa144bfcefdbed3dbf8 Mon Sep 17 00:00:00 2001 From: Zachary Gorman Date: Wed, 7 Aug 2024 11:33:25 -0700 Subject: [PATCH] Sean already did Spark! (facepalm). Implemented Sean's Spark! into new get-pc --- Private/BatchInvokes.ps1 | 73 +++-- Private/Connect-ISM.ps1 | 206 ++++++++++++++ Private/Find-ISMBusinessObject.ps1 | 254 +++++++++++++++++ Private/Get-CMDBFallback.ps1 | 172 ++++++++++-- Private/Get-Hostname.ps1 | 6 +- Private/Get-ISMBusinessObject.ps1 | 284 +++++++++++++++++++ Private/Invoke-CMDBweblaunch.ps1 | 14 +- Private/SHSPrinter.ps1 | 366 ++++++++++++++----------- Private/Search-ISMBusinessObject.ps1 | 393 +++++++++++++++++++++++++++ Private/Set-CMDBLocation.ps1 | 383 ++++++++++++++++++++++++++ Private/Spark.ps1 | 57 ---- Private/Update-ISMBusinessObject.ps1 | 167 ++++++++++++ Public/Get-PC.ps1 | 63 ++++- 13 files changed, 2145 insertions(+), 293 deletions(-) create mode 100644 Private/Connect-ISM.ps1 create mode 100644 Private/Find-ISMBusinessObject.ps1 create mode 100644 Private/Get-ISMBusinessObject.ps1 create mode 100644 Private/Search-ISMBusinessObject.ps1 create mode 100644 Private/Set-CMDBLocation.ps1 delete mode 100644 Private/Spark.ps1 create mode 100644 Private/Update-ISMBusinessObject.ps1 diff --git a/Private/BatchInvokes.ps1 b/Private/BatchInvokes.ps1 index 2b1d24e..d1d7c81 100644 --- a/Private/BatchInvokes.ps1 +++ b/Private/BatchInvokes.ps1 @@ -5,8 +5,8 @@ function Get-PCBatchInvoke { [string[]] $Computers ) - $SparkEnabled = Get-SparkEnabled - if ($SparkEnabled) { Connect-ISM } + $Tenant = (Connect-ISM)[1] + $Headers = (Connect-ISM)[0] #Region Script Block $sblock = { Write-Progress -Activity "Retrieving data from online computers" -Status $Env:COMPUTERNAME -PercentComplete 10 @@ -302,35 +302,33 @@ function Get-PCBatchInvoke { Default {$chassisType = "Unknown Model/Chassis"} } - if (${using:SparkEnabled}) { - $uri = "${using:SparkURL}/api/odata/businessobject/CIs`?`$filter=Name+eq+%27$($Env:COMPUTERNAME)%27" - try{ - $Query = Invoke-RestMethod -Method GET -uri $uri -headers ${using:SparkHeaders} - } catch { - $ExceptionErrors += $_.Exception.Message - } - $cmdbData = $Query.Value + $uri = "${using:Tenant}/api/odata/businessobject/cis`?`$filter=Name eq '$ENV:COMPUTERNAME'&`$top=1&`$skip=0" + try { + $Query = Invoke-RestMethod -Method GET -uri $uri -headers ${using:Headers} + } catch { + Write-Host $_.Exception.Message + } + $cmdbData = $Query.Value - if ($cmdbData.SHS_IsException -eq 'True') { - Write-host "***NOTICE: $($Env:COMPUTERNAME) is a Device Exception computer. Please check CMDB/Asset Mgmt prior to supporting this workstation. ***" -BackgroundColor Black -ForegroundColor Yellow - if($cmdb.SHS_IsVendorPC -eq 'True'){ - $vendor = "Yes | Non-standard SHS Image/Hardware" - } - else{ - $vendor = "No | Standard SHS Hardware" - } - - $delInfo = [PSCustomObject]@{ - Contact = $cmdbData.SHS_ExceptionContact - Vendor = $vendor - Description = $cmdbData.AssignedDescription - } - } + $LocationConstructors = @( + "SHS_AssetLocality", + "ivnt_Location", + "SHS_Floor", + "SHS_Department", + "SHS_LocationDetails" + ) - $locationData = $cmdbData.SHS_Floor + ' - ' + $cmdbData.SHS_Department + ' - ' + $cmdbData.SHS_LocationDetails + $LocationData = Foreach($Loc in $LocationConstructors){ + + if ($Loc -eq 'SHS_Floor'){ + $(if ($cmdbData.$Loc -match '-'){$cmdbData.$Loc.split('-')[-1] + " Floor"} else{$cmdbData.$Loc}) + } elseif (![string]::IsNullOrEmpty($cmdbData.$Loc)){ + $cmdbData.$Loc + } } - + $LocationData = $LocationData -join ' | ' + Write-Progress -Activity "Retrieving data from online computers" -Status $Env:COMPUTERNAME -PercentComplete 90 #Output # $i++ | ProgressBar $i $comp 'Generating Output' $NumberofComputers $PCID @@ -358,14 +356,13 @@ function Get-PCBatchInvoke { $obj | Add-Member -MemberType NoteProperty -Name 'TPM Status' -Value "$tpmStatus" $obj | Add-Member -MemberType NoteProperty -Name 'MBAM GPO' -Value "$gpostatus" $obj | Add-Member -MemberType NoteProperty -Name 'Printers' -Value "$printers" - if (${using:SparkEnabled}) { - $obj | Add-Member -MemberType NoteProperty -Name 'CMDB Location' -Value "$locationData" - } - if ($delInfo) { - $obj | Add-Member -MemberType NoteProperty -Name 'DEL Owner' -Value $delInfo.Contact - $obj | Add-Member -MemberType NoteProperty -Name 'DEL Vendor PC' -Value $delInfo.Vendor - $obj | Add-Member -MemberType NoteProperty -Name 'DEL Description' -Value $delInfo.Description - } + $obj | Add-Member -MemberType NoteProperty -Name 'CMDB Location' -Value "$LocationData" + if($cmdbData.SHS_IsException -eq 'True'){ + Write-host "***NOTICE: $ENV:COMPUTERNAME is a Device Exception computer. Please check CMDB/Asset Mgmt prior to supporting this workstation. ***" -BackgroundColor Black -ForegroundColor Yellow + $obj | Add-Member -MemberType NoteProperty -Name 'DEL Owner' -Value $cmdbData.SHS_ExceptionContact + $obj | Add-Member -MemberType NoteProperty -Name 'DEL Vendor PC' -Value $cmdbData.SHS_IsVendorPC + $obj | Add-Member -MemberType NoteProperty -Name 'DEL Description' -Value $cmdbData.SHS_ExceptionNotes + } return $obj } @@ -387,17 +384,16 @@ function Get-PCBatchInvoke { } BatchInvokesProgressBar -stage 2 $jobs = Invoke-Command -ScriptBlock $sblock -ComputerName $OnlineComputers -SessionOption (New-PSSessionOption -NoMachineProfile -OpenTimeout 45000) -AsJob - BatchInvokesProgressBar -stage 3 if($OfflineComputers.count -gt 0){ foreach($computer in $OfflineComputers){ $jobs += Start-Job -ScriptBlock ${function:SCCMQueryBlock} -ArgumentList $computer } } - BatchInvokesProgressBar -stage 4 $output += $jobs | Receive-Job -Wait -AutoRemoveJob | Select-Object * -ExcludeProperty RunspaceId, PSComputerName, PSShowComputerName + BatchInvokesProgressBar -stage 3 $itemIndex = 0 $contactedhosts = $output.Hostname - BatchInvokesProgressBar -stage 5 + BatchInvokesProgressBar -stage 4 $missedhosts = $OnlineComputers | Where-Object -FilterScript {$_ -notin $contactedhosts} if($missedhosts.count -gt 0){ foreach($computer in $missedhosts){ @@ -446,7 +442,6 @@ function BatchInvokesProgressBar { switch ($stage) { 1 { Write-Progress -Activity "Connecting to computers" -Status "$item ($currentItemIndex/$TotalItems)" -PercentComplete (($currentItemIndex/$TotalItems) * 100)} 2 { Write-Progress -Activity "Spinning up jobs" -PercentComplete ((20/100) * 100)} - 4 { Write-Progress -Activity "Querying SCCM for offline computers" -PercentComplete ((30/100) * 100)} 3 { Write-Progress -Activity "Awaiting Jobs" -PercentComplete ((60/100)*100)} 4 { Write-Progress -Activity "Querying SCCM for missed computers" -PercentComplete ((75/100) * 100)} Default {} diff --git a/Private/Connect-ISM.ps1 b/Private/Connect-ISM.ps1 new file mode 100644 index 0000000..56342e4 --- /dev/null +++ b/Private/Connect-ISM.ps1 @@ -0,0 +1,206 @@ +Function Connect-ISM { + <# + .SYNOPSIS + Primary authentication function leveraged by the module to connect to the Ivanti Service Manager API. + .DESCRIPTION + rimary authentication function leveraged by the module to connect to the Ivanti Service Manager API. Supports use of an API Key or SessionID authentication. + + PRD = Production + STG = Staging + UAT = User Acceptance Testing + .PARAMETER -Environment + The ISM Environment you wish to connect to. This is a validate set containing "PRD", "UAT", "STG" environments. + .PARAMETER -TenantPrefix + The ISM URL prefix for the Tenant. + .PARAMETER -AuthType + Parameter that defines what type of authentication to use. APIKey requires procuring an API Key from the ITSM Admins and is primarily relegated for use by administrators and unattended scripts. + + SessionID will prompt the user for Spark credentials and attempt to authenticate them directly. The default Role in the module is set for "ISAnalyst" but can be adjusted in the function. + .PARAMETER -ConnectionTest + Switch that when included will Invoke a WebRequest against the URL of the environment chosen. StatusDescription = OK will be returned on success. + .PARAMETER -AuthTypeCheck + Hidden parameter primarily intended for use internally by the module. This simply returns the value of AuthType and doesn't output any headers or make any API calls at all. + .INPUTS + A valid string. + .OUTPUTS + Returns the Tenant URI and the authorization headers necessary to make REST API calls to Ivanti Service Manager. + .NOTES + Version: 1.2 + Author: Sean Carlton + Creation Date: 04/26/2023 + Purpose/Change: Added AA_API_Key variable and check to leverage Get-AutomationPSCredential when $PSPrivateMetaData is not null. This variable is populated when a script is executing from Azure Automation (Cloud and Hybrid Worker), but otherwise is null. + + Version: 1.1 + Author: Sean Carlton + Creation Date: 10/06/2022 + Purpose/Change: Added initial SessionID support. To leverage this option requires a local login in Spark which most users do not have. A request has been submitted with Ivanti to determine if we can leverage OIDC auth instead. If so, the code will be in place to allow use of ISMTools without an API Key, using your own credentials for authentication to the API. Added the hidden -ConfigCheck switch which will return the Connect-ISM configuration currently in use. + + Version: 1.0 + Author: Sean Carlton + Creation Date: 08/16/2022 + Purpose/Change: Initial script development + .EXAMPLE + The below example demonstrates how to define the ISM/Spark environment you wish to connect to upon initial import. If the -ArgumentList parameter is not provided when Import-Module is ran, then the $Environment variable configured in Connect-ISM will be used to determine which environment to connect to. + + Import-Module 'ISMTools' -ArgumentList 'PRD' + + .EXAMPLE + + (Connect-ISM)[0] will return the authorization headers. + (Connect-ISM)[1] will return the Tenant URL. + (Connect-ISM)[2] will return the Authentication Type. + .EXAMPLE + + The -ConnectionTest switch will invoke a WebRequest against the Tenant URL the function is configured to connect to. It can be leveraged to check that your connection to ISM/Spark is valid. It returns all of the other properties but includes the StatusDescription of the Invoke-WebRequest call as well. + + Connect-ISM -ConnectionTest + + If Statement example to confirm you're connected to Spark before continuing: + + if ((Connect-ISM -ConnectionTest)[-1].StatusDescription -eq "OK"){ + Write-Host "Connected to Spark!" + } else { + Write-Warning "Failed to connect to Spark!" + Exit + } + .EXAMPLE + + The -ConfigCheck switch will return the current configuration of the module for reference. + + Connect-ISM -ConfigCheck + + #> + [CmdletBinding()] + param ( + [Parameter()] + [ValidateSet("STG", "UAT", "PRD")] + $Environment = $(if ($Module_Environment){ + $Module_Environment + }else{ + ## Define the Default Environment here. This will be used when no environment is passed by Import-Module + "PRD" + }), + + [Parameter()] + [ValidateSet("APIKey", "SessionID")] + [string]$AuthType = "APIKey", + + [Parameter()] + ## ServiceDeskAnalyst + ## ISAnalyst + ## Admin + ## DesktopTechnician + [string]$Role = "ISAnalyst", + + [Parameter()] + [string]$TenantPrefix = "samaritanhealth-amc", + + [Parameter()] + [switch]$ConnectionTest, + + [Parameter(DontShow)] + [switch]$ConfigCheck + ) + + BEGIN { + ##Initialize the Headers Hashtable. + + $Headers = @{ + "Content-Type" = "application/json" + "Authorization" = $null + "Accept" = "*/*" + "Accept-Encoding" = "gzip, deflate, br" + } + + ## If $PSPrivateMetaData is NOT null then this is running from an Azure Runbook/via Hybrid Worker. We'll attempt to pull the API key from the Credential Store of the automation account directly in that scenario. + $Azure_Execution = ($null -ne $PSPrivateMetadata) + + if ($Azure_Execution){ + $Cred_Obj = Get-AutomationPSCredential -Name "ISM $Environment API Key" + + if ($Cred_Obj){ + $AA_API_Key = (New-Object PSCredential $Cred_Obj.Username,$Cred_Obj.Password).GetNetworkCredential().Password; + } + } + ## Initialize the URL and API_Key based on $Environment selection. + ## If you don't have acess to the API Key for the environment you need, please reach out to the ITSM Team to request access. + Switch ($Environment){ + "STG" { + $API_Key = if($AA_API_Key){$AA_API_Key}else{''}; + $URL = "https://$TenantPrefix-$Environment.ivanticloud.com" + } + "UAT" { + $API_Key = if($AA_API_Key){$AA_API_Key}else{''}; + $URL = "https://$TenantPrefix-$Environment.ivanticloud.com" + } + "PRD" { + $API_Key = if($AA_API_Key){$AA_API_Key}else{'EB68123D62F8489295C807353C92D75B'}; + $URL = "https://$TenantPrefix.ivanticloud.com" + } + }#END $ENVIRONMENT SWITCH + }#END BEGIN BLOCK + + PROCESS { + + Switch ($AuthType){ + + "APIKey" { + ## Add the API_Key to the Headers + $Headers["Authorization"] = "rest_api_key=$API_Key" + } + + "SessionID" { + ## Prompt for Network Credentials if we don't have a valid SessionID/Token already stored. + if (!$ISMToolsModuleSessionID.SessionID){ + $SessionIDCreds = Get-Credential -User $ENV:Username -Message "Enter your network password to authenticate into Spark with the $Role role." + + $Body = @{ + "Tenant" = $URL.Split('/')[-1] + "Username" = $SessionIDCreds.Username + "Password" = $SessionIDCreds.GetNetworkCredential().Password + "Role" = $Role + } + + $JSONBody = $Body | ConvertTo-Json + + if ($SessionIDCreds.GetNetworkCredential().Password){ + try { + $URI = "$URL/api/rest/authentication/login" + $SessionID = Invoke-RestMethod -uri $URI -Method Post -Body $JSONBody -headers $Headers + } + catch {$_.Exception.Message} + } + + if ($SessionID -match "#"){ + $ISMToolsModuleSessionID.SessionID = $SessionID + } else { + Write-Host "Failed to authenticate with role $($Body.Role) with the provided credentials! Please check the credentials and role configured in the Connect-ISM function and try again." -ForegroundColor yellow + } + + $SessionIDCreds = $null + + }#End If No ISM_Tools_Module_Session_ID_Token + + ## Add the SessionID to the Headers + $Headers["Authorization"] = $ISMToolsModuleSessionID.SessionID + + } + + }#END AUTHTYPE SWITCH + + ## Run the ConnectionTest if -ConnectionTest was passed. + if ($ConnectionTest){ + $TestConnection = Invoke-WebRequest $URL -UseBasicParsing | Select-Object StatusDescription + } + + }#END PROCESS BLOCK + + END { + if ($ConfigCheck){ + return $Environment, $AuthType, $Role, $TenantPrefix, $URL, $API_Key + } else { + return $Headers, $URL, $AuthType, $TestConnection + } + }#END END-BLOCK + +}#End Connect-ISM Function diff --git a/Private/Find-ISMBusinessObject.ps1 b/Private/Find-ISMBusinessObject.ps1 new file mode 100644 index 0000000..fc51b8d --- /dev/null +++ b/Private/Find-ISMBusinessObject.ps1 @@ -0,0 +1,254 @@ +Function Find-ISMBusinessObject { +<# +.SYNOPSIS + Gets all records of a specified business object OR one particular record when a -RecID is provided. +.DESCRIPTION + Gets all records of a specified business object OR one particular record when a -RecID is provided. Additionally can retrieve metadata for any business object. +.PARAMETER -BusinessObject + The Business Object you are interacting with. (Incidents, CIs, CI__ActiveDirectorys, ServiceReqs, Locations, Employees, Manufacturers, StandardUserTeams, Problems, Changes, etc...) +.PARAMETER -RecID + The Record ID (RecID) of the specific record you wish to retrieve from the business object. +.PARAMETER -Top + Optional parameter that can be used to specify the number of records you'd like back from the business object. If the supplied number is greater than the total number of records or the -Top parameter is not specified, all records will be returned. +.PARAMETER -Metadata + Switch that results in the function returning the Business Object metadata as opposed to any of its records. Metadata contains lots of useful information about the business object such as the fields and quick actions that are associated with that business object. +.PARAMETER -PropertyList + Optional parameter that will query the supplied -BusinessObject metadata and return all of its fields/properties. +.PARAMETER -PropertyMatch + Optional parameter that allows for a match search against the -PropertyList. Useful for locating specific field names/properties of the business object. +.PARAMETER -DetailedOutput + Optional hidden parameter that includes some additional output during querying of the Business Object. Primarily used for debugging. +.INPUTS + Business Object and optionally a -RecID. +.OUTPUTS + The business object(s) are returned. +.NOTES + Version: 1.0 + Author: Sean Carlton + Creation Date: 08/16/2022 + Purpose/Change: Initial script development +.EXAMPLE + Get-ISMBusinessObject -BusinessObject incidents + + ======= + This example demonstrates getting all incident records. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject incidents -RecID "5788C7AB6E1E44AD8464FED18A510609" + + ======= + This example demonstrates getting a specific incident record based on its RecID. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject ci__activedirectorys -RecID "5788C7AB6E1E44AD8464FED18A510609" + + ======= + This example demonstrates getting all records of the CI__ActiveDirectorys extended business object. Extended business objects are formatted with two underscores(__) separating the parent business object from the child business object. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject cis + + ======= + This example demonstrates retrieving all ci records. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject cis -top 3 + + ======= + This example demonstrates retrieving the top 3 ci records. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject employees -metadata + + ======= + This example demonstrates querying the employees business object for its metadata. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject employees -PropertyMatch "email" + + ======= + This example demonstrates querying the employees business object for any fields/properties that match "email". This is useful for quickly locating specific fields you need to interact with. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject employees -PropertyList + + ======= + This example demonstrates querying the employees business object metadata for all of its associated fields/properties. A list of all fields will be returned. + ======= + +#> + [CmdletBinding(DefaultParameterSetName = 'BusinessObj')] + param ( + [Alias("BO")] + [Parameter(Mandatory = $true, + HelpMessage = 'Business Object to query for (Incidents, ServiceReqs, CIs, Changes, etc...)', + Position = 0)] + [string]$BusinessObject, + [Parameter()] + [string]$SearchQuery, + # [Parameter()] + # [string]$Top, + [Parameter(DontShow)] + [switch]$DetailedOutput, + ### API Connection Parameters ### + [Parameter(HelpMessage = 'Tenant URL', DontShow)] + [string]$Tenant = (Connect-ISM)[1], + [Parameter(HelpMessage = 'Authorization Header', DontShow)] + [hashtable]$Headers = (Connect-ISM)[0] + ) + + + # #Default the Top value to 100. (Maximum allowed record query per API call.) + # if (!$Top -or ($Top -gt 100)){ + # $InitialTop = "100" + # } else { + # $InitialTop = $Top + # } + + # $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$top=$InitialTop`$search=$SearchQuery" + $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$search=$SearchQuery" + + + #### FULLTEXT SEARCH + + + # ##TESTING + + # $URI = "$Tenant/api/rest/search/fulltext" + + # $ParamHash = @{ + # "Text" = $SearchQuery + # "ObjectType" = $BusinessObject + # "Top" = $InitialTop + # "Field" = "SHS_LocationDetails" + # # "Skip" = 0 + # } + + # $Query = Invoke-RestMethod -Method POST -uri $URI -headers $Headers -Body ($ParamHash | ConvertTo-JSON) + # $Query.Data + # ##TESTING + + try { + + $Query = Invoke-RestMethod -Method GET -uri $URI -headers $Headers + + if (!$Query){ + + Write-Host "No results returned for the $BusinessObject Business Object!" -ForegroundColor yellow + + } elseif ($Query.Edmx){ + + $Query.edmx.DataServices.schema | Add-Member -Value $BusinessObject -MemberType NoteProperty -Name "QueriedBusinessObject" -Force + + #Return the DataServices.schema of the object + $Results = $Query.edmx.DataServices.schema + + } elseif ($Query.Value){ + + if ($DetailedOutput){ + Write-Host "The $BusinessObject business object has $($Query.'@odata.count') total records." + } + + $Results = @() + + if (($Query.Value | Measure-Object).count -eq $Top -and ![string]::isnullorempty($Top)){ + $Query.'@odata.count' = $Top + $Results = $Query.Value + } elseif (($Query.Value | Measure-Object).count -gt $Top -and ![string]::isnullorempty($Top)){ + $Query.'@odata.count' = $Top + $Results = $Query.Value | Select-Object -First $Top + } elseif (($Query.Value | Measure-Object).count -lt $Top -and ![string]::isnullorempty($Top)){ + if (!$Top -gt $Query.'@odata.count'){ + $Query.'@odata.count' = $Top + } + $Results += $Query.Value + } else { + $Results += $Query.Value + } + + + if ($Results.Count -ne $Query.'@odata.count'){ + + $ExitLoop = $false + + Do { + + $Skip = $Results.Count + + $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$top=100&`$skip=$Skip`$search=$SearchQuery" + + $SkipQuery = Invoke-RestMethod -Method GET -uri $URI -headers $Headers + + if ($SkipQuery){ + $Results += $SkipQuery.Value + + if ($Results.count -gt $Top -and ![string]::isnullorempty($Top)){ + $Results = $Results | Select-Object -First $Top + $ExitLoop = $true + } + + } else { + write-warning "Failed to query for $BusinessObject! (Skip: $Skip | Total: $($Query.'@odata.count')) " + $ExitLoop = $true + } + + } until (($Results.Count -ge $Query.'@odata.count') -or $ExitLoop) + + } + + } else { + $Results = $Query + } + + #Friendly little helper loop to parse employee business object MemberOf HTML fields into a proper array. + if ($BusinessObject -eq "employees" -and (!$Metadata)){ + + Foreach ($Employee in $Results){ + + $Employee | Add-Member -MemberType NoteProperty -Value @() -Name "SHS_MemberOfParsed" -force + + if ($Employee.SHS_MemberOf){ + $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOf.replace("
","%").split("%").trim() + $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOfParsed | Sort-Object + } + + } + + } + + if ($PropertyList -and $Results -or $PropertyMatch -and $Results){ + + if ($Results.EntityType.count){ + $BO_Properties = $Results.EntityType[-1].Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name" + } else { + $BO_Properties = $Results.EntityType.Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name" + } + + if ($PropertyMatch){ + $BO_Properties = $BO_Properties | Where-Object {$_.Name -match $PropertyMatch} + } + + return $BO_Properties + + } else { + return $Results + } + + +} + +catch { + write-warning "Failed querying for $BusinessObject!" +} + + + + +}#END FUNCTION + + +New-Alias -Name Find-ISMBO -Value Find-ISMBusinessObject -Force + + + + diff --git a/Private/Get-CMDBFallback.ps1 b/Private/Get-CMDBFallback.ps1 index c3229c3..40bd720 100644 --- a/Private/Get-CMDBFallback.ps1 +++ b/Private/Get-CMDBFallback.ps1 @@ -3,31 +3,87 @@ function Get-CMDBFallback { $comp ) - #$cmdbData = Get-LANDeskCMDBItem -name $comp - $cmdbData = Get-SparkCI $comp + ## $cmdbData = Get-LANDeskCMDBItem -name $comp + $cmdbData = Search-ISMBO -bo cis -filter "Name eq '$comp'" -RawFilter + if(!$cmdbData){ + if($comp.Length -gt 5){ + $asset = $comp[-5..-1] -join "" + ## $cmdbData = Get-LANDeskCMDBItem -AssetTag $asset + $cmdbData = Search-ISMBO -bo cis -filter "AssetTag eq '$asset'" -RawFilter + if($cmdbData){ + $comp = $cmdbData.values.Title + if(Test-Connection $comp){ + $getpcData = get-pc $comp + return $getpcData + } + } + } + } + $status = $cmdbData.Status if(!$status){ $status = 'No CMDB/SCCM Record Located' - } - else{ + } else{ $status = $cmdbData.Status + ' (CMDB Data)' } - $locationData = $cmdbData.SHS_Floor + ' - ' + $cmdbData.SHS_Department + ' - ' + $cmdbData.SHS_LocationDetails + $LocationConstructors = @( + "SHS_AssetLocality", + "ivnt_Location", + "SHS_Floor", + "SHS_Department", + "SHS_LocationDetails" + ) - $phoneNumber = $cmdbData.LoginName + $LocationData = Foreach($Loc in $LocationConstructors){ + if ($Loc -eq 'SHS_Floor'){ + $(if ($cmdbData.$Loc -match '-'){$cmdbData.$Loc.split('-')[-1] + " Floor"} else{$cmdbData.$Loc}) + } elseif (![string]::IsNullOrEmpty($cmdbData.$Loc)){ + $cmdbData.$Loc + } + } + + $LocationData = $LocationData -join ' | ' + + # $locationData = $cmdbData.SHS_AssetLocality + " | " + $cmdbData.ivnt_Location + " | " + $(if ($cmdbData.SHS_Floor -match '-'){$cmdbData.SHS_Floor.split('-')[-1] + " Floor"}) + " | " + $cmdbData.SHS_Department + " | " + $cmdbData.SHS_LocationDetails + # if($cmdbData.values._SHSLocation3.Length -gt $cmdbData.values._SHSCalcLocationString.Length){ + # $locationData = $cmdbData.values._SHSLocation3 + # } + # else{ + # $locationData = $cmdbData.values._SHSCalcLocationString + # } if($cmdbData.CIType -eq 'MobileDevice'){ - $props = [Ordered]@{ + + $cmdbData = Get-ISMBO -bo ci__mobiledevicess -RecID $cmdbData.RecId + + # $props = [Ordered]@{ + # Hostname = $cmdbData.Title + # 'MAC' = $cmdbData.values._SHSMACAddress + # Model = $cmdbData.values._Model + ' (' + $cmdbData.Values._SHSChasisRef + ')' + # 'OS' = $cmdbData.Values._SHSOperatingSystem + # 'Asset Tag' = $cmdbData.values._SHSAssetTag + # 'Service Tag' = $cmdbData.values._SerialNumber + # 'MDM Platform' = $cmdbData.values._SHSMDMPlatform + # 'Phone Number' = $cmdbData.values._SHSPhoneNumber + # 'Cellular Carrier' = $cmdbData.values._SHSCellularCarrier + # 'Cellular Voice' = $cmdbData.values._SHSCellularVoice + # 'Cellular Data' = $cmdbData.values._SHSCellularData + # 'CMDB Location' = $locationData + # } + + $props = [Ordered]@{ Hostname = $cmdbData.Name 'MAC' = $cmdbData.MACAddress - Model = $cmdbData.Model + ' (' + $cmdbData.ChassisType + ')' - 'OS' = $cmdbData.OperatingSystem + Model = $cmdbData.Model + ' (' + $cmdbData.ivnt_assetsubtype + ')' + 'OS' = $cmdbData.SHS_AssignedOperatingSystem + " " + $cmdbData.OSMajorVersion 'Asset Tag' = $cmdbData.AssetTag 'Service Tag' = $cmdbData.SerialNumber 'MDM Platform' = $cmdbData.SHS_MdmPlatform - 'Phone Number' = $cmdbData.PhoneNumber #TODO fix this, idk why Spark! doesn't return this + 'MDM Config' = $cmdbData.SHS_ConfigProfile + 'MDM Last Seen' = $cmdbData.SHS_LastSeen + 'Phone Number' = $cmdbData.PhoneNumber 'Cellular Carrier' = $cmdbData.SHS_Carrier 'Cellular Voice' = $cmdbData.SHS_IsCellularVoice 'Cellular Data' = $cmdbData.SHS_IsCellularData @@ -37,38 +93,106 @@ function Get-CMDBFallback { $obj = New-Object -TypeName PSObject -Property $props } else{ + # $props = [Ordered]@{ + # Hostname = "$comp" + # Status = $status + # 'Current User' = "Not Available" + # 'Last User(s)' = $null + # 'IP | MAC' = $cmdbData.values._IPAddress + " | " + $cmdbData.values._SHSMACAddress + # Model = $cmdbData.values._Model + ' (' + $cmdbData.Values._SHSChasisRef + ')' + # 'OS' = $cmdbData.Values._SHSOperatingSystem + # 'OS Build' = $null + # 'BIOS Ver' = $null + # Encryption = $null + # 'Free Space' = $cmdbData.Values._AvailableDiskSpace + ' GB / ' + $cmdbData.Values._HardDiskSize + ' GB' + # RAM = $cmdbData.Values._RAM + " GB " + # 'SSO Client' = "Not Available" + # 'Kiosk Role' = "Not Available" + # 'Asset Tag' = $cmdbData.values._SHSAssetTag + # 'Service Tag' = $cmdbData.values._SerialNumber + # 'Last Reboot' = $null + # Printers = $null + # 'CMDB Location' = $locationData + # } + + if ($cmdbData.CIType -eq "Computer"){ + $cmdbData = Get-ISMBO -BO ci__computers -recid $cmdbData.Recid + $memoryData = Search-ISMBO -bo frs_CIComponent__memorys -Filter "ParentLink_RecId eq '$($cmdbData.RecId)'" -RawFilter + $diskData = Search-ISMBO -bo frs_cicomponent__logicalstorages -Filter "ParentLink_RecId eq '$($cmdbdata.recid)'" -Rawfilter + if ($memoryData){ + $MemoryTotal = ($Memorydata.Memorysize | measure-object -sum).sum + } + + } + $props = [Ordered]@{ Hostname = "$comp" Status = $status 'Current User' = "Not Available" - 'Last User(s)' = $null + 'Last User(s)' = $cmdbData.LoginName 'IP | MAC' = $cmdbData.IPAddress + " | " + $cmdbData.MACAddress - Model = $cmdbData.Model + ' (' + $cmdbData.ChassisType + ')' - 'OS' = $cmdbData.OperatingSystem - 'OS Build' = $null - 'BIOS Ver' = $null - Encryption = $null - 'Free Space' = $cmdbData.Values._AvailableDiskSpace + ' GB / ' + $cmdbData.Values._HardDiskSize + ' GB' - RAM = $cmdbData.Values._RAM + " GB " + Model = $cmdbData.Model + ' (' + $cmdbData.ivnt_assetsubtype + ')' + 'OS' = $cmdbData.OperatingSystem + " " + $cmdbData.OSMajorVersion + 'OS Build' = $cmdbData.OSMajorVersion + 'BIOS Ver' = $cmdbData.BIOSVersion + " ($($cmdbData.BiosDate))" + Encryption = $cmdbData.ivnt_EncryptionState + 'Free Space' = $diskData.FreeSpace + ' GB / ' + $diskData.capacity + ' GB' + 'RAM' = $MemoryTotal+ " GB " 'SSO Client' = "Not Available" - 'Kiosk Role' = "Not Available" + # 'Kiosk Role' = "Not Available" 'Asset Tag' = $cmdbData.AssetTag 'Service Tag' = $cmdbData.SerialNumber 'Last Reboot' = $null Printers = $null 'CMDB Location' = $locationData + } + + if ($cmdbData.SHS_IsKiosk){ + $props.add("Kiosk Role", $cmdbdata.SHS_KioskRoles) } $obj = New-Object -TypeName PSObject -Property $props } Write-Host "`n`nPulling cached CMDB data for $comp." -ForegroundColor Yellow - Write-Host "Last CMDB Update: ",$cmdbData.values.LastUpdate -ForegroundColor Yellow + Write-Host "Last CMDB Update: ",$cmdbData.LastModDateTime -ForegroundColor Yellow if($cmdbData.SHS_IsException -eq 'True'){ - $delInfo = Get-CMDBDELInfo $cmdbData - $obj | Add-Member -MemberType NoteProperty -Name 'DEL Owner' -Value $delInfo.Contact - $obj | Add-Member -MemberType NoteProperty -Name 'DEL Vendor PC' -Value $delInfo.Vendor - $obj | Add-Member -MemberType NoteProperty -Name 'DEL Description' -Value $delInfo.Description + ## $delInfo = Get-CMDBDELInfo $cmdbData + $obj | Add-Member -MemberType NoteProperty -Name 'DEL Owner' -Value $cmdbData.SHS_ExceptionContact + $obj | Add-Member -MemberType NoteProperty -Name 'DEL Vendor PC' -Value $cmdbData.SHS_IsVendorPC + $obj | Add-Member -MemberType NoteProperty -Name 'DEL Description' -Value $cmdbData.SHS_ExceptionNotes } return $obj +} + +function Get-CMDBData { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $comp, + + [string] + $asset + ) + + if($asset -match "\w \w"){ + $asset = $asset -split ' ' + $asset = $asset[0] + } + + ## $cmdb = Get-LANDeskCMDBItem -Name $comp + $cmdb = Search-ISMBO -bo cis -filter "Name eq '$comp'" -RawFilter + if ($null -eq $cmdb){ + try { + ## $cmdb = Get-LANDeskCMDBItem -AssetTag $asset + $cmdb = Search-ISMBO -bo cis -filter "AssetTag eq '$asset'" -RawFilter + + } catch{ $cmdb = $null } + + } + + + return $cmdb + } \ No newline at end of file diff --git a/Private/Get-Hostname.ps1 b/Private/Get-Hostname.ps1 index 55b7d8d..b21ccc1 100644 --- a/Private/Get-Hostname.ps1 +++ b/Private/Get-Hostname.ps1 @@ -3,7 +3,11 @@ function Get-Hostname ([string]$name) { if ($name.Length -eq 5) { $res = Get-AssetConversion $name if ($res) { return $res,'' } - else { $errMsg += "$name Asset Tag not in SMBIOS or CMDB. "} + try { + $cmdbData = Search-ISMBO -bo cis -filter "AssetTag eq '$name'" -RawFilter + } catch { $cmdbData = $null } + if ( $cmdbData ) { return $cmdbData.values.title, '' } + else { $errMsg += "$name Asset Tag not in SMBIOS or CMDB. " } } # Regex to match IP Address brought to you by https://stackoverflow.com/a/36760050 if ($name -match "^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$") { diff --git a/Private/Get-ISMBusinessObject.ps1 b/Private/Get-ISMBusinessObject.ps1 new file mode 100644 index 0000000..ac60eae --- /dev/null +++ b/Private/Get-ISMBusinessObject.ps1 @@ -0,0 +1,284 @@ +Function Get-ISMBusinessObject { +<# +.SYNOPSIS + Gets all records of a specified business object OR one particular record when a -RecID is provided. +.DESCRIPTION + Gets all records of a specified business object OR one particular record when a -RecID is provided. Additionally can retrieve metadata for any business object. +.PARAMETER -BusinessObject + The Business Object you are interacting with. (Incidents, CIs, CI__ActiveDirectorys, ServiceReqs, Locations, Employees, Manufacturers, StandardUserTeams, Problems, Changes, etc...) +.PARAMETER -RecID + The Record ID (RecID) of the specific record you wish to retrieve from the business object. +.PARAMETER -Top + Optional parameter that can be used to specify the number of records you'd like back from the business object. If the supplied number is greater than the total number of records or the -Top parameter is not specified, all records will be returned. +.PARAMETER -Metadata + Switch that results in the function returning the Business Object metadata as opposed to any of its records. Metadata contains lots of useful information about the business object such as the fields and quick actions that are associated with that business object. +.PARAMETER -PropertyList + Optional parameter that will query the supplied -BusinessObject metadata and return all of its fields/properties. +.PARAMETER -PropertyMatch + Optional parameter that allows for a match search against the -PropertyList. Useful for locating specific field names/properties of the business object. +.PARAMETER -DetailedOutput + Optional hidden parameter that includes some additional output during querying of the Business Object. Primarily used for debugging. +.INPUTS + Business Object and optionally a -RecID. +.OUTPUTS + The business object(s) are returned. +.NOTES + Version: 1.0 + Author: Sean Carlton + Creation Date: 08/16/2022 + Purpose/Change: Initial script development +.EXAMPLE + Get-ISMBusinessObject -BusinessObject incidents + + ======= + This example demonstrates getting all incident records. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject incidents -RecID "5788C7AB6E1E44AD8464FED18A510609" + + ======= + This example demonstrates getting a specific incident record based on its RecID. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject ci__activedirectorys -RecID "5788C7AB6E1E44AD8464FED18A510609" + + ======= + This example demonstrates getting all records of the CI__ActiveDirectorys extended business object. Extended business objects are formatted with two underscores(__) separating the parent business object from the child business object. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject cis + + ======= + This example demonstrates retrieving all ci records. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject cis -top 3 + + ======= + This example demonstrates retrieving the top 3 ci records. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject employees -metadata + + ======= + This example demonstrates querying the employees business object for its metadata. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject employees -PropertyMatch "email" + + ======= + This example demonstrates querying the employees business object for any fields/properties that match "email". This is useful for quickly locating specific fields you need to interact with. + ======= +.EXAMPLE + Get-ISMBusinessObject -BusinessObject employees -PropertyList + + ======= + This example demonstrates querying the employees business object metadata for all of its associated fields/properties. A list of all fields will be returned. + ======= + +#> + [CmdletBinding(DefaultParameterSetName = 'BusinessObj')] + param ( + [Alias("BO")] + [Parameter(Mandatory = $true, + HelpMessage = 'Business Object to query for (Incidents, ServiceReqs, CIs, Changes, etc...)', + Position = 0)] + [Parameter( + ParameterSetName = 'BusinessObj')] + [Parameter( + ParameterSetName = 'RecID')] + [Parameter( + ParameterSetName = 'Metadata')] + [string]$BusinessObject, + + [Parameter( + ParameterSetName = 'RecID')] + [string]$RecID, + + [Parameter( + ParameterSetName = 'BusinessObj')] + [string]$Top, + + [Parameter( + ParameterSetName = 'Metadata')] + [switch]$Metadata, + + [Parameter( + ParameterSetName = 'Metadata')] + [string]$PropertyMatch, + + [Parameter( + ParameterSetName = 'Metadata')] + [switch]$PropertyList, + + [Parameter( + ParameterSetName = 'BusinessObj')] + [Parameter( + ParameterSetName = 'RecID')] + [Parameter( + ParameterSetName = 'Metadata')] + [Parameter(DontShow)] + [switch]$DetailedOutput, + + ### API Connection Parameters ### + [Parameter(HelpMessage = 'Tenant URL', DontShow)] + [string]$Tenant = (Connect-ISM)[1], + [Parameter(HelpMessage = 'Authorization Header', DontShow)] + [hashtable]$Headers = (Connect-ISM)[0] + ) + + + switch ($PSCmdlet.ParameterSetName) { + 'Metadata' { + $URI = "$Tenant/api/odata/$BusinessObject/`$metadata" + break + } + + 'RecId' { + $URI = "$Tenant/api/odata/businessobject/$BusinessObject('$RecID')" + break + } + + "BusinessObj" { + + If ($BusinessObject -match "."){ + $BusinessObject = $BusinessObject.replace(".","__") + } + + #Default the Top value to 100. (Maximum allowed record query per API call.) + if (!$Top -or ($Top -gt 100)){ + $InitialTop = "100" + } else { + $InitialTop = $Top + } + + $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$top=$InitialTop" + break + } + } + + + try { + + $Query = Invoke-RestMethod -Method GET -uri $URI -headers $Headers + + if (!$Query){ + + Write-Host "No results returned for the $BusinessObject Business Object!" -ForegroundColor yellow + + } elseif ($Query.Edmx){ + + $Query.edmx.DataServices.schema | Add-Member -Value $BusinessObject -MemberType NoteProperty -Name "QueriedBusinessObject" -Force + + #Return the DataServices.schema of the object + $Results = $Query.edmx.DataServices.schema + + } elseif ($Query.Value){ + + if ($DetailedOutput){ + Write-Host "The $BusinessObject business object has $($Query.'@odata.count') total records." + } + + $Results = @() + + if (($Query.Value | Measure-Object).count -eq $Top -and ![string]::isnullorempty($Top)){ + $Query.'@odata.count' = $Top + $Results = $Query.Value + } elseif (($Query.Value | Measure-Object).count -gt $Top -and ![string]::isnullorempty($Top)){ + $Query.'@odata.count' = $Top + $Results = $Query.Value | Select-Object -First $Top + } elseif (($Query.Value | Measure-Object).count -lt $Top -and ![string]::isnullorempty($Top)){ + if (!$Top -gt $Query.'@odata.count'){ + $Query.'@odata.count' = $Top + } + $Results += $Query.Value + } else { + $Results += $Query.Value + } + + + if ($Results.Count -ne $Query.'@odata.count'){ + + $ExitLoop = $false + + Do { + + $Skip = $Results.Count + + $URI = "$Tenant/api/odata/businessobject/$BusinessObject`?`$top=100&`$skip=$Skip" + + $SkipQuery = Invoke-RestMethod -Method GET -uri $URI -headers $Headers + + if ($SkipQuery){ + $Results += $SkipQuery.Value + + if ($Results.count -gt $Top -and ![string]::isnullorempty($Top)){ + $Results = $Results | Select-Object -First $Top + $ExitLoop = $true + } + + } else { + write-warning "Failed to query for $BusinessObject! (Skip: $Skip | Total: $($Query.'@odata.count')) " + $ExitLoop = $true + } + + } until (($Results.Count -ge $Query.'@odata.count') -or $ExitLoop) + + } + + } else { + $Results = $Query + } + + #Friendly little helper loop to parse employee business object MemberOf HTML fields into a proper array. + if ($BusinessObject -eq "employees" -and (!$Metadata)){ + + Foreach ($Employee in $Results){ + + $Employee | Add-Member -MemberType NoteProperty -Value @() -Name "SHS_MemberOfParsed" -force + + if ($Employee.SHS_MemberOf){ + $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOf.replace("
","%").split("%").trim() + $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOfParsed | Sort-Object + } + + } + + } + + if ($PropertyList -and $Results -or $PropertyMatch -and $Results){ + + if ($Results.EntityType.count){ + $BO_Properties = $Results.EntityType[-1].Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name" + } else { + $BO_Properties = $Results.EntityType.Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name" + } + + if ($PropertyMatch){ + $BO_Properties = $BO_Properties | Where-Object {$_.Name -match $PropertyMatch} + } + + return $BO_Properties + + } else { + return $Results + } + + +} + +catch { + write-warning "Failed querying for $BusinessObject!" +} + + + + +}#END FUNCTION + + +New-Alias -Name Get-ISMBO -Value Get-ISMBusinessObject -Force + + + + diff --git a/Private/Invoke-CMDBweblaunch.ps1 b/Private/Invoke-CMDBweblaunch.ps1 index 9221611..66a8285 100644 --- a/Private/Invoke-CMDBweblaunch.ps1 +++ b/Private/Invoke-CMDBweblaunch.ps1 @@ -5,12 +5,16 @@ function Invoke-CMDBweblaunch { } Write-Host "Querying CMDB..." - try { $cmdbData = Get-LANDeskCMDBItem -Name $comp } catch { $cmdbData = $null} + try { $cmdbData = Search-ISMBO -bo cis -filter "name eq '$comp'" -RawFilter } catch { $cmdbData = $null} if($null -eq $cmdbData){ Write-Host "CMDB hostname mismatch" -ForegroundColor Yellow Write-Host "Running Get-PC Lookup for asset tag" $getpcData = get-pc $comp - try { $cmdbData = Get-LANDeskCMDBItem -AssetTag $getpcData.'Asset Tag'} catch{ $cmdbData = $null } + try { + + ## $cmdbData = Get-LANDeskCMDBItem -AssetTag $getpcData.'Asset Tag' + $cmdbData = Search-ISMBO -bo cis -filter "assettag eq '$($getpcData.'Asset Tag')'" -RawFilter + } catch{ $cmdbData = $null } if($null -eq $cmdbData){ Write-Warning "Unable to find record with assset tag in CMDB" Write-Host "Unable to launch record page" -ForegroundColor Yellow @@ -20,8 +24,10 @@ function Invoke-CMDBweblaunch { Write-Host "CMDB record found - launching Landesk page in IE" - $uri = "https://shslandesk/WebAccess/wd/object/open.rails?class_name=_CMDBManagement.Call&key=" - $fulluri = $uri + $cmdbData.key + # $uri = "https://shslandesk/WebAccess/wd/object/open.rails?class_name=_CMDBManagement.Call&key=" + # $fulluri = $uri + $cmdbData.key + $fulluri = "https://samaritanhealth-amc.ivanticloud.com/login.aspx?Scope=ObjectWorkspace&CommandId=Search&ObjectType=CI%23" + $cmdbdata.CIType + "&CommandData=RecId%2C%3D%2C0%2C" + $cmdbdata.RecId + "%2Cstring%2CAND%2C%7C" + Start-Process $fulluri return } \ No newline at end of file diff --git a/Private/SHSPrinter.ps1 b/Private/SHSPrinter.ps1 index f55364d..ed2e5f4 100644 --- a/Private/SHSPrinter.ps1 +++ b/Private/SHSPrinter.ps1 @@ -21,46 +21,69 @@ function Get-SHSPrinter { foreach ($printer in $printers) { #Gets the printer ip and full domain name - $hit = $false - $domains = @('.gsrmc.int.samhealth.net', '.avery.int.samhealth.net', '.sagh.int.samhealth.net', '.snlh.int.samhealth.net', '.slch.int.samhealth.net', '.spch.int.samhealth.net') - $result = Resolve-DnsName $printer -ErrorAction SilentlyContinue - - if ($null -eq $result) { - $hit = $false - foreach ($domain in $domains) { - $search = "$printer$domain" - $result = Resolve-DnsName $search -ErrorAction SilentlyContinue - if ($null -ne $result) { - $hit = $true - $name = $result.Name - $ip = $result.IPAddress - - - } - - } - - if ($null -eq $result) { - if ($hit -eq $false) { - $ip = $null - $name = $null - } - } - } - else { - $name = $result.Name - $ip = $result.IPAddress - - } - - $result = [ordered]@{Hostname = $printer - IP = $ip - Path = $name - } - + $result = Get-PrinterIP $printer + #CMDB Data - if ($null -ne $result.IP) { + # $cmdbRecord = Get-LANDeskCMDBItem -Name $printer + $cmdbRecord = Search-ISMBO -BO cis -filter "Name eq '$Printer'" -RawFilter + + $LocationConstructors = @( + "SHS_AssetLocality", + "ivnt_Location", + "SHS_Floor", + "SHS_Department", + "SHS_LocationDetails" + ) + + $LocationData = Foreach($Loc in $LocationConstructors){ + + if ($Loc -eq 'SHS_Floor'){ + $(if ($cmdbRecord.$Loc -match '-'){$cmdbRecord.$Loc.split('-')[-1] + " Floor"} else{$cmdbRecord.$Loc}) + } elseif (![string]::IsNullOrEmpty($cmdbRecord.$Loc)){ + $cmdbRecord.$Loc + } + } + + $LocationData = $LocationData -join ' | ' + + # $locationData = $cmdbRecord.SHS_AssetLocality + " | " + $cmdbRecord.ivnt_Location + " | " + + " | " + $cmdbRecord.SHS_Department + " | " + $cmdbRecord.SHS_LocationDetails + # if($cmdbRecord.values._SHSLocation3.Length -gt $cmdbRecord.values._SHSCalcLocationString.Length){ + # $locationData = $cmdbRecord.values._SHSLocation3 + # } + # else{ + # $locationData = $cmdbRecord.values._SHSCalcLocationString + # } + if($cmdbRecord){ + # $CMDB_POA = $cmdbRecord.values._SHSPOANumber + # $CMDB_AssetTag = $cmdbRecord.values._SHSAssetTag + # $CMDB_Location = $locationData + # $CMDB_MAC = $cmdbRecord.values._SHSMACAddress + # $CMDB_model = $cmdbRecord.values._Model + # $CMDB_serial = $cmdbRecord.values._SerialNumber + # $CMDB_IP = $cmdbRecord.values._IPAddress + + ### Spark Properties + $CMDB_POA = $cmdbRecord.SHS_POANumber + $CMDB_AssetTag = $cmdbRecord.AssetTag + $CMDB_Location = $locationData + $CMDB_MAC = $cmdbRecord.MACAddress + $CMDB_model = $cmdbRecord.Model + $CMDB_serial = $cmdbRecord.SerialNumber + $CMDB_IP = $cmdbRecord.IPAddress + } + else{ + $CMDB_POA = "*CMDB Mismatch - check CMDB*" + $CMDB_AssetTag = "*CMDB Mismatch - check CMDB*" + $CMDB_Location = "*CMDB Mismatch - check CMDB*" + $CMDB_MAC = "*CMDB Mismatch - check CMDB*" + $CMDB_model = "*CMDB Mismatch - check CMDB*" + $CMDB_serial = "*CMDB Mismatch - check CMDB*" + $CMDB_IP = "*CMDB Mismatch - check CMDB*" + } + + if($result.IP -ne $null) { + $printerip = $result.IP $domainName = $result.Path @@ -68,7 +91,9 @@ function Get-SHSPrinter { #checks to see if the printer is online $online = Test-Connection $printerip -ErrorAction SilentlyContinue - if ($online) { + + + if($online){ #opens snmp connection to the printer $snmp.open($printerip, 'public', 2, 3000) @@ -91,7 +116,7 @@ function Get-SHSPrinter { try { if ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2') -match 'Toner|Cartridge|ink') { $color = 'Yes' } else { $color = 'No' } } catch { $color = 'No' } # TRAYS - try { $trays = $($snmp.GetTree('.1.3.6.1.2.1.43.8.2.1.13') | Where-Object { $_ -notlike 'print*' }) -join ';' } catch { $trays = $null } + try { $trays = $($snmp.GetTree('.1.3.6.1.2.1.43.8.2.1.13') | ? {$_ -notlike 'print*'}) -join ';' } catch { $trays = $null } # SERIAL try { $serial = $snmp.Get('.1.3.6.1.2.1.43.5.1.1.17.1') } catch { $serial = $null } @@ -119,12 +144,12 @@ function Get-SHSPrinter { try { $model = $snmp.Get('.1.3.6.1.4.1.10642.200.19.7.0').toupper() } catch { $model = $null } try { $MAC = $snmp.Get('.1.3.6.1.4.1.10642.20.10.20.10.2.1.5.2').toupper() } catch { $MAC = $null } try { $serial = $snmp.Get('.1.3.6.1.4.1.10642.200.19.5.0') } catch { $serial = $null } - # try { $status = $snmp.Get('.1.3.6.1.4.1.10642.200.4.1.0') } catch { $status = $null } + # try { $status = $snmp.Get('.1.3.6.1.4.1.10642.200.4.1.0') } catch { $status = $null } $model = "Zebra $model" #STATUS $uri = $domainName $html = Invoke-WebRequest -uri $uri - $raw = $html.rawcontent + $raw = $html.rawcontent $raw -match '([A-Z])\w+<.F' | Out-Null $status = ($Matches[0] -split '<')[0] #split off error condition @@ -143,10 +168,10 @@ function Get-SHSPrinter { '^hp' { try { $name = $snmp.Get('.1.3.6.1.4.1.11.2.4.3.5.46.0').toupper() } catch { $name = $null } try { $status = $snmp.Get('.1.3.6.1.4.1.11.2.3.9.1.1.3.0') } catch { $status = $null } - if ($MAC -eq '') { + if($MAC -eq ''){ $pMAC = $snmp.Get('.1.3.6.1.4.1.11.2.4.3.1.12.1.2.5') $MAC = ($pMAC -split " ")[-1] - $MAC = ($MAC -replace '(..)', '$1:').trim(':') + $MAC = ($MAC -replace '(..)','$1:').trim(':') } #TONER @@ -156,78 +181,78 @@ function Get-SHSPrinter { HP Toner Cur: 1.3.6.1.2.1.43.11.1.1.9.1.1 HP Toner Max: 1.3.6.1.2.1.43.11.1.1.8.1.1 #> - try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.1') -split ' ')[0] } catch { $tonerColor = $null } - try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.1') } catch { $tonerLvl = $null } - try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.1') } catch { $tonerMax = $null } - try { $toner = $tonerLvl / $tonerMax } catch { $toner = $null } + try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.1') -split ' ')[0]} catch { $tonerColor = $null} + try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.1')} catch {$tonerLvl = $null} + try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.1')} catch {$tonerMax = $null} + try { $toner = $tonerLvl/$tonerMax} catch {$toner = $null} [int]$toner = $toner * 100 - $supplies = "$tonerColor" + ":$toner% " - if ($color -eq 'Yes') { - try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2') -split ' ')[0] } catch { $tonerColor = $null } - try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.2') } catch { $tonerLvl = $null } - try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.2') } catch { $tonerMax = $null } - try { $toner = $tonerLvl / $tonerMax * 100 } catch { $toner = $null } - $supplies += "$tonerColor" + ":$toner% " + $supplies = "$tonerColor"+":$toner% " + if($color -eq 'Yes'){ + try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2') -split ' ')[0]} catch { $tonerColor = $null} + try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.2')} catch {$tonerLvl = $null} + try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.2')} catch {$tonerMax = $null} + try { $toner = $tonerLvl/$tonerMax * 100} catch {$toner = $null} + $supplies += "$tonerColor"+":$toner% " - try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.3') -split ' ')[0] } catch { $tonerColor = $null } - try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.3') } catch { $tonerLvl = $null } - try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.3') } catch { $tonerMax = $null } - try { $toner = $tonerLvl / $tonerMax * 100 } catch { $toner = $null } - $supplies += "$tonerColor" + ":$toner% " + try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.3') -split ' ')[0]} catch { $tonerColor = $null} + try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.3')} catch {$tonerLvl = $null} + try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.3')} catch {$tonerMax = $null} + try { $toner = $tonerLvl/$tonerMax * 100} catch {$toner = $null} + $supplies += "$tonerColor"+":$toner% " - try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.4') -split ' ')[0] } catch { $tonerColor = $null } - try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.4') } catch { $tonerLvl = $null } - try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.4') } catch { $tonerMax = $null } - try { $toner = $tonerLvl / $tonerMax * 100 } catch { $toner = $null } - $supplies += "$tonerColor" + ":$toner% " + try { $tonerColor = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.4') -split ' ')[0]} catch { $tonerColor = $null} + try { $tonerLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.4')} catch {$tonerLvl = $null} + try { $tonerMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.4')} catch {$tonerMax = $null} + try { $toner = $tonerLvl/$tonerMax * 100} catch {$toner = $null} + $supplies += "$tonerColor"+":$toner% " - try { $supplyName = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.6') -split ' ')[0] } catch { $tonerColor = $null } - if ($supplyName -contains 'Fuser' -or $supplyName -contains 'Maint') { - try { $supplyPartNumber = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.6') } catch { $supplyPartNumber = $null } + try { $supplyName = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.6') -split ' ')[0]} catch { $tonerColor = $null} + if($supplyName -contains 'Fuser' -or $supplyName -contains 'Maint'){ + try { $supplyPartNumber = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.6')} catch { $supplyPartNumber = $null} $supplyPartNumber -match '110V-(?.+), ' | Out-Null $supplyPartNumber = $Matches.Name - try { $mainLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.6') } catch { $mainLvl = $null } - try { $mainMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.6') } catch { $mainMax = $null } - if ($mainMax -gt 1) { - try { $maintKit = $mainLvl / $mainMax } catch { $maintKit = $null } + try {$mainLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.6')} catch {$mainLvl = $null} + try {$mainMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.6')} catch {$mainMax = $null} + if($mainMax -gt 1){ + try {$maintKit = $mainLvl / $mainMax} catch {$maintKit = $null} [int]$maintKit = $maintKit * 100 - if ($null -ne $maintKit) { - $supplies += "| $supplyName" + ":$maintKit%[$supplyPartNumber]" + if($null -ne $maintKit){ + $supplies += "| $supplyName" +":$maintKit%[$supplyPartNumber]" } } } - else { - try { $supplyName = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.5') -split ' ')[1] } catch { $tonerColor = $null } - try { $supplyPartNumber = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.5') } catch { $supplyPartNumber = $null } + else{ + try { $supplyName = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.5') -split ' ')[1]} catch { $tonerColor = $null} + try { $supplyPartNumber = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.5')} catch { $supplyPartNumber = $null} $supplyPartNumber -match '110V-(?.+), ' | Out-Null $supplyPartNumber = $Matches.Name - try { $mainLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.5') } catch { $mainLvl = $null } - try { $mainMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.5') } catch { $mainMax = $null } - if ($mainMax -gt 1) { - try { $maintKit = $mainLvl / $mainMax } catch { $maintKit = $null } + try {$mainLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.5')} catch {$mainLvl = $null} + try {$mainMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.5')} catch {$mainMax = $null} + if($mainMax -gt 1){ + try {$maintKit = $mainLvl / $mainMax} catch {$maintKit = $null} [int]$maintKit = $maintKit * 100 - if ($null -ne $maintKit) { - $supplies += "| $supplyName" + ":$maintKit%[$supplyPartNumber]" + if($null -ne $maintKit){ + $supplies += "| $supplyName" +":$maintKit%[$supplyPartNumber]" } } } } - else { - try { $supplyName = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2') -split ' ')[0] } catch { $supplyName = $null } - try { $supplyPartNumber = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2') } catch { $supplyPartNumber = $null } + else{ + try { $supplyName = ($snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2') -split ' ')[0]} catch { $supplyName = $null} + try { $supplyPartNumber = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.6.1.2')} catch { $supplyPartNumber = $null} $supplyPartNumber -match '110V-(?.+), ' | Out-Null $supplyPartNumber = $Matches.Name - try { $mainLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.2') } catch { $mainLvl = $null } - try { $mainMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.2') } catch { $mainMax = $null } - if ($mainMax -gt 1) { - try { $maintKit = $mainLvl / $mainMax } catch { $maintKit = $null } + try {$mainLvl = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.9.1.2')} catch {$mainLvl = $null} + try {$mainMax = $snmp.Get('.1.3.6.1.2.1.43.11.1.1.8.1.2')} catch {$mainMax = $null} + if($mainMax -gt 1){ + try {$maintKit = $mainLvl / $mainMax} catch {$maintKit = $null} [int]$maintKit = $maintKit * 100 - if ($null -ne $maintKit) { - $supplies += "| $supplyName" + ":$maintKit%[$supplyPartNumber]" + if($null -ne $maintKit){ + $supplies += "| $supplyName" +":$maintKit%[$supplyPartNumber]" } } } @@ -254,7 +279,7 @@ function Get-SHSPrinter { } } - else { + else{ $status = "Offline" $MAC = $CMDB_MAC $model = $CMDB_model @@ -265,46 +290,57 @@ function Get-SHSPrinter { } $props = [Ordered]@{ - Machine = $printer - Status = $status - IP = $printerip - DomainName = $domainName - MAC = $MAC - Model = $model - Serial = $serial + Machine = $printer + Status = $status + IP = $printerip + DomainName = $domainName + MAC = $MAC + Model = $model + Serial = $serial #Comment = $comment - Color = $color - Trays = $trays + Color = $color + Trays = $trays #Features = $features #SystemDescription = $sysdescr0 #Addresses = $addr - PageCount = $pagecount - Supplies = $supplies + PageCount = $pagecount + Supplies = $supplies + 'CMDB POA' = $CMDB_POA + 'CMDB AssetTag' = $CMDB_AssetTag + 'CMDB Location' = $CMDB_Location } $obj = New-Object -TypeName PSObject -Property $props $snmp.close() return $obj - } - } - - $props = [Ordered]@{ - Machine = $printer - Status = 'No DNS Entry' - IP = 'No DNS Entry' - DomainName = 'No DNS Entry' - MAC = 'No DNS Entry' - Model = 'No DNS Entry' - Serial = 'No DNS Entry' - #Comment = $comment - Color = 'No DNS Entry' - Trays = 'No DNS Entry' - PageCount = 'No DNS Entry' - } - $obj = New-Object -TypeName PSObject -Property $props - $snmp.close() - return $obj + } + } + if($CMDB_IP){ + $offlineIP = 'CMDB IP - ' + $CMDB_IP + } + else{ + $offlineIP = 'No DNS Entry' + } + $props = [Ordered]@{ + Machine = $printer + Status = 'No DNS Entry' + IP = $offlineIP + DomainName = 'No DNS Entry' + MAC = $CMDB_MAC + Model = $CMDB_model + Serial = $CMDB_serial + #Comment = $comment + Color = 'No DNS Entry' + Trays = 'No DNS Entry' + PageCount = 'No DNS Entry' + 'CMDB POA' = $CMDB_POA + 'CMDB AssetTag' = $CMDB_AssetTag + 'CMDB Location' = $CMDB_Location + } + $obj = New-Object -TypeName PSObject -Property $props + $snmp.close() + return $obj } } @@ -313,69 +349,69 @@ function Get-SHSPrinter { function Get-PrinterIP { param ( - [parameter(ValueFromPipeline)] - [string]$printer - ) - $hit = $false - $domains = @('.gsrmc.int.samhealth.net', '.avery.int.samhealth.net', '.sagh.int.samhealth.net', '.snlh.int.samhealth.net', '.slch.int.samhealth.net', '.spch.int.samhealth.net') - $result = Resolve-DnsName $printer -ErrorAction SilentlyContinue + [parameter(ValueFromPipeline)] + [string]$printer + ) + $hit = $false + $domains = @('.gsrmc.int.samhealth.net','.avery.int.samhealth.net','.sagh.int.samhealth.net','.snlh.int.samhealth.net','.slch.int.samhealth.net','.spch.int.samhealth.net') + $result = Resolve-DnsName $printer -ErrorAction SilentlyContinue - if ($null -eq $result) { - $hit = $false - foreach ($domain in $domains) { - $search = "$printer$domain" - $result = Resolve-DnsName $search -ErrorAction SilentlyContinue - if ($null -ne $result) { - $hit = $true - $name = $result.Name - $ip = $result.IPAddress + if($result -eq $null){ + $hit = $false + foreach ($domain in $domains) { + $search = "$printer$domain" + $result = Resolve-DnsName $search -ErrorAction SilentlyContinue + if($result -ne $null){ + $hit = $true + $name = $result.Name + $ip = $result.IPAddress - } + } - } + } - if ($null -eq $result) { - if ($hit -eq $false) { - $ip = $null - $name = $null - } - } - } - else { - $name = $result.Name - $ip = $result.IPAddress + if($result -eq $null){ + if($hit -eq $false){ + $ip = $null + $name = $null + } + } + } + else{ + $name = $result.Name + $ip = $result.IPAddress - } + } - $props = [ordered]@{Hostname = $printer - IP = $ip - Path = $name - } - $obj = New-Object -TypeName PSObject -Property $props + $props = [ordered]@{Hostname=$printer + IP=$ip + Path=$name + } + $obj = New-Object -TypeName PSObject -Property $props - return $obj + return $obj -} + } -function Start-SHSPrinterWeb { + function Start-SHSPrinterWeb { param( $printer ) process { - if ($printer.DomainName -ne 'No DNS Entry' -and $printer.MAC -ne 'Offline') { + if($printer.DomainName -ne 'No DNS Entry' -and $printer.MAC -ne 'Offline'){ $domainName = $printer.DomainName - Start-Process "http://$domainName" + start "http://$domainName" $mac = $printer.MAC - Start-Sleep -Seconds 1 - Start-Process "https://shsorion/Orion/UDT/EndpointDetails.aspx?NetObject=UE-MAC:VAL=$mac" + Sleep -Seconds 1 + start "https://shsorion/Orion/UDT/EndpointDetails.aspx?NetObject=UE-MAC:VAL=$mac" } - else { + else{ Write-Warning "Unable to launch web interface" } } -} + } diff --git a/Private/Search-ISMBusinessObject.ps1 b/Private/Search-ISMBusinessObject.ps1 new file mode 100644 index 0000000..33eb68a --- /dev/null +++ b/Private/Search-ISMBusinessObject.ps1 @@ -0,0 +1,393 @@ + +Function Search-ISMBusinessObject { +<# +.SYNOPSIS + Searches business objects for records matching the supplied criteria/filter. +.DESCRIPTION + This function will fetch a business object(s) for records that equal the supplied criteria in the -Filter parameter. + + Only the following eight operators are supported when fetching business object records using the filter function: + eq - Returns true if the first value is equal to the second value. + ne - Returns true if the first value is not equal to the second value. + lt - Returns true if the first value is less than the second value. + le - Returns true if the first value is less than or equal to the second value. + gt - Returns true if the first value is greater than the second value. + ge - Returns true if the first value is greater than or equal to the second value. + or - Returns true if either one of its left or right sub-expressions evaluates to true. + and - Returns true if both its left and right sub-expressions evaluate to true. +.PARAMETER -BusinessObject + The Business Object you are interacting with. (Incidents, CIs, CI__ActiveDirectorys, ServiceReqs, Locations, Employees, Manufacturers, StandardUserTeams, Problems, Changes, etc...) +.PARAMETER -Filter + The filter containing your search string. This parameter allowed queries to be constructed using a more "friendly" syntax and additionally supports submitting the API call as entered explicitly. + + The filter must be formatted properly to be parsed correctly by the API. Please see the examples in the help for more information. +.PARAMETER -RawFilter + An optional switch parameter that can be used to force the function to pass exactly the -filter string as supplied with no additional parsing. When this switch isn't used, the function will default to parsing the filter in an attempt to make it easier to format queries which includes requiring operators be preceded by hyphens. (-eq, -and, etc...) +.PARAMETER -Testing + Hidden optional parameter that is useful for outputting additional debugging information when troubleshooting the function. +.PARAMETER -DetailedOutput + Hidden optional parameter that will return the API URI submitted for outputting additional debugging information when troubleshooting the function. +.INPUTS + Parent and Child RecIDs, Action and Relationship. +.OUTPUTS + Returns the newly created Relationship Business Object on success. +.NOTES + Version: 1.0 + Author: Sean Carlton + Creation Date: 08/16/2022 + Purpose/Change: Initial script development +.EXAMPLE + Search-ISMBusinessObject -BusinessObject employees -Filter "primaryemail -eq johnsmith@email.com" + + ======= + This example demonstrates searching the employees business object where the primaryemail property is equal to johnsmith@email.com. + ======= +.EXAMPLE + Search-ISMBusinessObject -BusinessObject cis -Filter "createdby -eq jsmith -and name -eq 3M - File Extract Process" + + ======= + This example demonstrates searching the cis business object where the createdby user is equal to jsmith AND the name of the record is equal to 3M - File Extract Process. + ======= +.EXAMPLE + Search-ISMBusinessObject -BusinessObject incidents -Filter "incidentnumber -eq 11504" + + ======= + This example demonstrates searching the incidents business object where the incident number is equal to 11504. + ======= +.EXAMPLE + Search-ISMBusinessObject -BusinessObject locations -Filter "name -eq SAGH Campus - ''88 Bldg" + + ======= + This example demonstrates using the apostrophe as an escape character. The Location Name of SAGH Campus - '88 Bldg contains an apostrophe which is parsed by the REST API and so it must be escaped with an apostrophe (') so the search query executes successfully. + ======= + .EXAMPLE + Search-ISMBusinessObject -BusinessObject cis -Filter "name eq 'IL GEM 4000 2 SAGH'" -RawFilter + + ======= + This example demonstrates searching the cis business object where the name of the record is equal to IL GEM 4000 2 SAGH. The -RawFilter switch will submit the provided filter exactly as typed to the API. Notice that there is no hypen before the operator (eq) and the value is wrapped in single quotes. + ======= +.EXAMPLE + Search-ISMBusinessObject -BusinessObject cis -Filter "CreatedDateTime -gt 2022-07-24T23:42:48Z" + + ======= + This example demonstrates searching the cis business object where the CreatedDateTime of the record is greater than 2022-07-24T23:42:48Z. + ======= +.EXAMPLE + Search-ISMBusinessObject -BusinessObject cis -Filter "CreatedDateTime gt 2022-07-24T23:42:48Z" -RawFilter + + ======= + This example demonstrates searching the cis business object where the CreatedDateTime of the record is greater than 2022-07-24T23:42:48Z but using the -RawFilter switch parameter. Notice there is no hyphen before the operator (gt) and that in this particular instance, the value supplied is NOT wrapped in single quotes. Not all fetch calls to the API require the value be wrapped in single quotes depending on the type of field being queried for. + ======= +.EXAMPLE + Search-ISMBusinessObject -businessobject frs_data_workflow_historys -filter "BlockException ne '`$NULL'" -RawFilter + + ======= + This example demonstrates searching a business object and evaluating a NULL property value. The null property must be in all caps and be preceded with $ for the REST API to parse it properly. + ======= +#> + [CmdletBinding()] + param ( + [Alias("BO")] + [Parameter( + HelpMessage = 'Business Object Type you are Querying Against (Incidents, ServiceReqs, cis, etc...)', + Mandatory = $true)] + [string]$BusinessObject, + + [Parameter( + HelpMessage = 'Format: Property -eq ''Value'', + Ex: displayname -eq ''John Batman''', + Mandatory=$true)] + [string]$Filter, + + [Parameter(Helpmessage = " + Ex: Search-ISMBusinessObject -BusinessObject cis -Filter ""createdby eq 'scarlton' and name eq 'IL GEM 4000 2 SAGH'"" -RawFilter + ")] + [switch]$RawFilter, #// Switch to pass the filter value literally. This accounts for scenarios where the default -filter parsing fails/doesn't work. Primary differences include no hyphen prefix for Operators and wrapping the value in single quotes for most values (but not all! If your query is failing, try wrapping the value in single quotes and try again without.) + + [Parameter(HelpMessage="Optional hidden parameter that will output additional information useful for debugging the function.", + Mandatory=$false, + DontShow)] + [switch]$Testing, + + [Parameter(HelpMessage="Optional hidden parameter that can be used to ouput additional information.", + Mandatory=$false, + DontShow)] + [switch]$DetailedOutput, + + ### API Connection Parameters ### + [Parameter(HelpMessage = 'Tenant URL', DontShow)] + [string]$Tenant = (Connect-ISM)[1], + [Parameter(HelpMessage = 'Authorization Header', DontShow)] + [hashtable]$Headers = (Connect-ISM)[0] + + ) + + + +BEGIN { + + #Presume the syntax is valid to start + $ValidSyntax = $true + + ### Initialize an array for all errors/exceptions + $ExceptionErrors = @() + + ### Query for BusinessObject Metadata Properties + $BOMetaDataProperties = (Get-ISMBusinessObject -BusinessObject $BusinessObject -Metadata) + + ### If we have no BOMetaDataProperties then an invalid BusinessObject was supplied. + if (!$BOMetaDataProperties){ + $ValidSyntax = $false + Write-Host "Unable to query Metadata for the $BusinessObject Business Object! Check the name and try again." -ForegroundColor yellow + } elseif ($BOMetaDataProperties.EntityType.count){ + $BOMetaDataProperties = $BOMetaDataProperties.EntityType[-1].Property.Name | Sort-Object + } else { + $BOMetaDataProperties = $BOMetaDataProperties.EntityType.Property.Name + } + + # Parse the filter accordingly based on whether the -RawFilter switch was provided or not. + if ($RawFilter -and $ValidSyntax){ + + $FilterEncodes = [ORDERED]@{ + RAW_Filter = $Filter #\\Filter literally as entered by the user. + + ## Commented out on 01/16/23. Caused false-positive errors getting logged in $Error + ## HTTP_Parsed_RAW = [System.Web.HTTPUtility]::UrlEncode(($Filter))#\\URL-Encoded with single quotes around the query. + ## Commented out on 01/16/23 + } + + } elseif ($ValidSyntax) { + #Define the list of valid filter operators + $ValidOperators = @("-eq","-ne","-lt","-gt","-ge","-or","-and") + + #Trim any whitespace from the beginning and end of the submitted filter query. + $Filter = $Filter.trim() + + #Split out the provided filter on the spaces. + $FilterSplit = ($Filter -split '(?= )').trim() + + #Parse the array. We can tell the Property Names and Operators apart from the values by querying for the metadata and comparing the operators to the supported operators. + $i = 0 + $SplitArray = @() + Foreach ($Split in $FilterSplit){ + + $SplitObj = [PSCustomObject]@{ + Split = $Split + Type = $null + NextSplit = $null + SplitQuoted = $null + SplitOpenQuote = $null + SplitCloseQuote = $null + Count = $i + ValidBOProperty = $false + } + + if ($Split -in $ValidOperators){ + $SplitObj.Split = $SplitObj.Split.Replace("-","") + $SplitObj.Type = "Operator" + $SplitObj.NextSplit = "Value" + } elseif ($SplitObj.Split -in $BOMetaDataProperties) { + $SplitObj.ValidBOProperty = $true + $SplitObj.Type = "Property" + $SplitObj.NextSplit = "Operator" + }else { + $SplitObj.Type = "Value" + $SplitObj.NextSplit = "Unknown" + } + + $SplitObj.SplitQuoted = "'" + $SplitObj.Split + "'" + $SplitObj.SplitOpenQuote = "'" + $SplitObj.Split + $SplitObj.SplitCloseQuote = $SplitObj.Split + "'" + + $SplitArray += $SplitObj + + $i++ + } + + ## Removed on 8/15/22 to ensure the fallback to the raw filter functionality works + # if ("Operator" -notin $SplitArray.Type){ + # Write-host "Your filter query appears to have no valid operators. Operators must be formatted with a preceding hyphen and be in the list of valid operators: $($ValidOperators -join ", ")`n`nExample: ""LoginID -eq $($ENV:Username)""`nExample: ""CreatedDateTime -lt 2022-01-24T23:42:48Z""`n`nIf your filter query is correct as formatted, please use the -RawFilter switch to query the API with your literal query string." -ForegroundColor yellow + # $ValidSyntax = $false + # } + + # Build the filter string based on the Type derived. + $FilterStringQuoted = @() + $FilterStringUnquoted = @() + $i = 0 + foreach ($Split in $SplitArray){ + + #Add the raw value of the Split to the unquoted filter string. + $FilterStringUnquoted += $Split.Split + + if ($Split.Type -eq "Value"){ + + If ($SplitArray[$i-1].Type -eq "Operator" -and $SplitArray[$i+1].Type -eq "Operator") { + $FilterStringQuoted += $Split.SplitQuoted + } elseif ($SplitArray[$i-1].Type -eq "Operator" -and (!$SplitArray[$i+1].Type)){ + $FilterStringQuoted += $Split.SplitQuoted + } elseif ($SplitArray[$i-1].Type -eq "Operator"){ + $FilterStringQuoted += $Split.SplitOpenQuote + } elseif ($SplitArray[$i+1].Type -eq "Operator") { + $FilterStringQuoted += $Split.SplitCloseQuote + } elseif ($SplitArray[$i+1].Type -eq "Property"){ + $FilterStringQuoted += $Split.SplitCloseQuote + } elseif ($SplitArray[$i+1].Type -eq "Property") { + $FilterStringQuoted += $Split.SplitCloseQuote + } elseif ((!$SplitArray[$i+1].Type)){ + $FilterStringQuoted += $Split.SplitCloseQuote + } else { + $FilterStringQuoted += $Split.Split + } + + } else { + $FilterStringQuoted += $Split.Split + } + + $i++ + } + + #Create an ordered hashtable with all of the parsed queries. + $FilterEncodes = [ORDERED]@{ + HTTP_Parsed_With_Quotes = [System.Web.HTTPUtility]::UrlEncode(($FilterStringQuoted -join " "))#\\URL-Encoded with single quotes around the query. + HTTP_Parsed_No_Quotes = [System.Web.HTTPUtility]::UrlEncode(($FilterStringUnquoted -join " ")) #\\URL-Encoded without single quotes around the query. + RAW_With_Quotes = ($FilterStringQuoted -join " ") #\\Raw with single-quotes parsed around the query. + RAW_No_Quotes = ($FilterStringUnquoted -join " ") #\\Raw except perators have had the preceding - removed. + RAW_Filter = $Filter #\\Filter literally as entered by the user. + } + + if ("Property" -notin $SplitArray.Type){ + $ValidSyntax = $false + + $PropertyHelper = Get-ISMBusinessObject -BusinessObject $BusinessObject -PropertyMatch $($FilterSplit[0]) + + $PropertyHelperMsg = $null + if ($PropertyHelper){ + $PropertyHelperMsg = "`nPerhaps one of these matching fields is what you're looking for:`n$($PropertyHelper.Name -join "`n")`n" + } + + Write-Host "The ""$($FilterSplit[0])"" property provided does not appear to be a valid property name on the $BusinessObject BusinessObject!`n$PropertyHelperMsg`nTo see a list of ALL valid properties run the following command:`n`nGet-ISMBusinessObject -BusinessObject $BusinessObject -PropertyList" -ForegroundColor yellow + + # if ($DetailedOutput){ + # # Write-Host "The ""$($FilterSplit[0])"" property provided does not appear to be a valid property name on the $BusinessObject BusinessObject!`n`n$BusinessObject has the following properties available: + # # $($BOMetaDataProperties -join ", ")" -ForegroundColor yellow + + + # } else { + # Write-Host "The ""$($FilterSplit[0])"" property provided does not appear to be a valid property name on the $BusinessObject BusinessObject!`n`nTo see a list of valid properties run the following command:`n`nGet-ISMBusinessObject -BusinessObject $BusinessObject -PropertyList" -ForegroundColor yellow + # } + } + + }#End Else-if No -RawFilter Switch + +}#END BEGIN + + PROCESS { + + if ($ValidSyntax){ + #Initialize the results array. + $Results = @() + + #Iterate through every encoded filter until we get results. This is to account for the various ways in which the API will fail to parse the filter query. + Foreach ($FilterEncode in $FilterEncodes.Keys){ + + if ($Testing){ + Write-Host "Testing with $FilterEncode!" -foregroundcolor yellow + } + + $uri = "$Tenant/api/odata/businessobject/$BusinessObject`?`$filter=$($FilterEncodes[$FilterEncode])&`$top=100&`$skip=0" + + try{ + $Query = $null + $Query = Invoke-RestMethod -Method GET -uri $uri -headers $headers + } + + catch { + $ExceptionErrors += $_.Exception.Message + } + + If ($Query.'@odata.count'){ + + if ($Testing){ + Write-Host "Success using $FilterEncode!`n`nFilter Passed: $($FilterEncodes[$FilterEncode])`n`nWorking URI: $URI + " -foregroundcolor green + } + + $Results += $Query.Value + + if ($Results.Count -lt $Query.'@odata.count'){ + + Do { + + $Skip = $Results.Count + + $uri = "$Tenant/api/odata/businessobject/$BusinessObject`?`$filter=$($FilterEncodes[$FilterEncode])&`$top=100&`$skip=$Skip" + + $SkipQuery = Invoke-RestMethod -Method GET -uri $uri -headers $headers + + if ($SkipQuery){ + $Results += $SkipQuery.Value + } else { + + if ($Skip -lt $Query.'@odata.count'){ + #If there's no SkipQuery but we have more results than the original queried @odata.count we won't bother with a warning. This can occur when looping through records when a new record is created during the loop. + Write-Warning "Failed query for $BusinessObject! (Skip: $Skip | Total: $($Query.'@odata.count'))" + } + + $ExitLoop = $true + + } + + } until (($Results.Count -ge $Query.'@odata.count') -or $ExitLoop) + + } + + #Friendly little helper loop to parse employee business object MemberOf HTML fields into a proper array. + if ($BusinessObject -eq "employees"){ + + Foreach ($Employee in $Results){ + + $Employee | Add-Member -MemberType NoteProperty -Value @() -Name "SHS_MemberOfParsed" -force + + if ($Employee.SHS_MemberOf){ + $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOf.replace("
","%").split("%").trim() + $Employee.SHS_MemberOfParsed = $Employee.SHS_MemberOfParsed | Sort-Object + } + + } + + } + + break + + } else { + + if ($DetailedOutput){ + Write-Host "Query Error: $($ExceptionErrors[-1])" -ForegroundColor red + } + + } + + }#END FOREACH + }#END IF VALID-SYNTAX + }#END PROCESS + + END { + + if ($DetailedOutput -and !$Results){ + + Write-host "No results found querying $BusinessObject with the provided filter!" -ForegroundColor yellow + + } elseif ($DetailedOutput -and $Results){ + + Write-Host "Successful query!`n`nAPI URI: $uri`n`n" -foregroundcolor green + return $Results + + } else { + return $Results + } + + }#END END-BLOCK + +}#END Search-ISMBusinessObject Function + +New-Alias -Name Search-ISMBO -Value Search-ISMBusinessObject -Force \ No newline at end of file diff --git a/Private/Set-CMDBLocation.ps1 b/Private/Set-CMDBLocation.ps1 new file mode 100644 index 0000000..f54b07a --- /dev/null +++ b/Private/Set-CMDBLocation.ps1 @@ -0,0 +1,383 @@ +function Set-CMDBLocation { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Hostname + ) + + process { + + + + + try { + ## $cmdbData = Get-LANDeskCMDBItem -Name $Hostname + $cmdbRecord = Search-ISMBO -BO cis -filter "Name eq '$Hostname'" -RawFilter + } catch { $cmdbData = $null} + if($null -eq $cmdbData){ + $getpcData = Get-PCBatchInvoke $Hostname + $asset = $getpcData.'Asset Tag' + $asset = $asset -split ' ' + $asset = $asset[0] + try { + + ## $cmdbData = Get-LANDeskCMDBItem -AssetTag $asset + $cmdbData = Search-ISMBO -BO cis -filter "AssetTag eq '$asset'" -RawFilter + + } catch{ $cmdbData = $null } + if($null -eq $cmdbData){ + Write-Warning "Unable to find record with that hostname or assset tag in CMDB" + return + } + } + $oldLocation = $cmdbData.Values._SHSLocation3 + $key = $cmdbData.key + Write-Host "Hostname: $hostname" + Write-Host "Current Location: $oldLocation" + $newLocation = Read-Host "New Location" + + try {Set-LANDeskCMDBWorkstation -LocationDetails $newLocation -Key $key | Out-Null } + catch { + Write-Warning "Unable to change location data" + return + } + try { + ## $cmdbData = Get-LANDeskCMDBItem -Name $Hostname + $cmdbData = Search-ISMBO -BO cis -filter "Hostname eq '$Hostname'" -RawFilter + + } catch { $cmdbData = $null} + if($null -eq $cmdbData){ + try { + ##$cmdbData = Get-LANDeskCMDBItem -AssetTag $asset + $cmdbData = Search-ISMBO -BO cis -filter "AssetTag eq '$asset'" -RawFilter + } catch{ $cmdbData = $null } + if($null -eq $cmdbData){ + Write-Warning "Unable to find record with that hostname or assset tag in CMDB" + return + } + } + + $newLocation = $cmdbData.values._SHSLocation3 + Write-Host "Location now set to: $newLocation" -ForegroundColor Green + } +} + +function Set-LANDeskCMDBLocation { + [CmdletBinding(DefaultParameterSetName="Default")] + param ( + [Parameter(HelpMessage = 'The Name of the new CMDB item', + Mandatory = $true + )] + [string]$Name, + [Parameter(HelpMessage = 'Enter the Location Details for the CMDB Item')] + [string]$LocationDetails, + + [Parameter(HelpMessage = 'Enter the GUID for the CMDB Item')] + [string]$Key, + + [Parameter(HelpMessage = 'Specifying Server', + Mandatory = $false, + DontShow = $true)] + [string] $server = $(Connect-LANDesk)[1], + [Parameter(HelpMessage = 'Specifying LanDesk Framework for api', + Mandatory = $false, + DontShow = $true)] + [string] $framework = $(Connect-LANDesk)[2], + [Parameter(HelpMessage = 'Specifying LanDesk Headers for api', + Mandatory = $false, + DontShow = $true)] + [hashtable] $Headers = $(Connect-LANDesk)[0] + ) + + begin { + } + + process { + [object[]]$Values = $null + + $Title = @{} + $Title.Name = "Title" + $Title.Value = $Name + [object[]]$Values += $Title + + <# + $_ConfigTypesCategory = @{} + $_ConfigTypesCategory.Name = "_ConfigTypesCategory" + $_ConfigTypesCategory.Value = '9dadd9f9-ca5d-4be6-93cc-6c8745fff615' + $Values += $_ConfigTypesCategory + #> + $LocationDetails + $_SHSLocation3 = @{} + $_SHSLocation3.Name = "_SHSLocation3" + $_SHSLocation3.Value = $LocationDetails + $Values += $_SHSLocation3 + + $body = [ordered]@{} + $body.class_name = '_CMDBManagement.Call' + $Body.originalValues = $Values + $body.formValues = $Values + $body = $body | ConvertTo-Json + + $uri = 'http://' + $server + '/' + $framework + '/api/form?class_name=_CMDBManagement.Call&key=' + $Key + '&function_name=Edit&v=*' + Invoke-RestMethod -Uri $uri -Headers $headers -Body $Body -Method Patch + + } + + end { + } +} + +function Set-LANDeskCMDBWorkstation { + [CmdletBinding(DefaultParameterSetName="Default")] + param ( + [Parameter(HelpMessage = 'The Name of the new CMDB item')] + [string]$Name, + + [Parameter(HelpMessage = 'Enter the PO for the CMDB Item')] + [string]$PO, + + [Parameter(HelpMessage = 'Enter the Physical Location for the CMDB Item')] + [string]$PhysicalLocation, + + [Parameter(HelpMessage = 'Enter the Asset Tag for the CMDB Item')] + [string]$AssetTag, + + [Parameter(HelpMessage = 'Enter the RequestID for the CMDB Item')] + [string]$RequestID, + + [Parameter(HelpMessage = 'Enter the OperatingSystem for the CMDB Item')] + [string]$OperatingSystem, + + [Parameter(HelpMessage = 'Enter the ChassisType for the CMDB Item')] + [string]$ChassisType, + + [Parameter(HelpMessage = 'Enter the Total Hard Drive Size for the CMDB Item')] + [string]$HDD, + + [Parameter(HelpMessage = 'Enter the RAM for the CMDB Item')] + [string]$RAM, + + [Parameter(HelpMessage = 'Enter the Hard drive space Avalible for the CMDB Item')] + [string]$HDDAvail, + + [Parameter(HelpMessage = 'Enter the IPAddress for the CMDB Item')] + [string]$IPAddress, + + [Parameter(HelpMessage = 'Enter the KioskRole for the CMDB Item')] + [string]$KioskRole, + + [Parameter(HelpMessage = 'Enter the Model for the CMDB Item')] + [string]$Model, + + [Parameter(HelpMessage = 'Enter the SerialNumber for the CMDB Item')] + [string]$SerialNumber, + + [Parameter(HelpMessage = 'Enter the Notes for the CMDB Item')] + [string]$Notes, + + [Parameter(HelpMessage = 'Enter the OSPatching for the CMDB Item')] + [string]$OSPatching, + + [Parameter(HelpMessage = 'Enter the Patch Schedule for the CMDB Item')] + [string]$PatchSchedule, + + [Parameter(HelpMessage = 'Enter the Patch Notes for the CMDB Item')] + [string]$PatchNotes, + + [Parameter(HelpMessage = 'Enter the Location Details for the CMDB Item')] + [string]$LocationDetails, + + [Parameter(HelpMessage = 'Enter the GUID for the CMDB Item', + Mandatory = $true)] + [string]$Key, + + [Parameter(HelpMessage = 'Specifying Server', + Mandatory = $false, + DontShow = $true)] + [string] $server = $(Connect-LANDesk)[1], + [Parameter(HelpMessage = 'Specifying LanDesk Framework for api', + Mandatory = $false, + DontShow = $true)] + [string] $framework = $(Connect-LANDesk)[2], + [Parameter(HelpMessage = 'Specifying LanDesk Headers for api', + Mandatory = $false, + DontShow = $true)] + [hashtable] $Headers = $(Connect-LANDesk)[0] + ) + + begin { + } + + process { + [object[]]$Values = $null + + if($Name){ + $Title = @{} + $Title.Name = "Title" + $Title.Value = $Name + [object[]]$Values += $Title + } + + <# $_ConfigTypesCategory = @{} + $_ConfigTypesCategory.Name = "_ConfigTypesCategory" + $_ConfigTypesCategory.Value = '9dadd9f9-ca5d-4be6-93cc-6c8745fff615' + $Values += $_ConfigTypesCategory #> + + if ($PO) { + $_PO = @{} + $_PO.Name = "_PO" + $_PO.Value = $PO + $Values += $_PO + } + + # Look up table + if ($PhysicalLocation) { + $Location = Search-LANDeskIssueLocation -Location $PhysicalLocation + $_CILocation = @{} + $_CILocation.Name = "_CILocation" + $_CILocation.Value = $Location.Key + $Values += $_CILocation + } + if ($AssetTag) { + $_SHSAssetTag = @{} + $_SHSAssetTag.Name = "_SHSAssetTag" + $_SHSAssetTag.Value = $AssetTag + $Values += $_SHSAssetTag + } + + if ($RequestID) { + $_RequestID = @{} + $_RequestID.Name = "_RequestID" + $_RequestID.Value = $RequestID + $Values += $_RequestID + } + # Look up table + if ($OperatingSystem) { + if($OperatingSystem -match 'Windows 10'){ + $OS = '2ad72fd5-8d4e-4510-b5ea-f94b3a2cda08' + } + elseif ($OperatingSystem -match 'Windows 7') { + $OS = 'f127ceee-074b-4cc1-8a71-f8c02a9a9bd1' + } + else { + $OS = '00000000-0000-0000-0000-000000000000' + } + $_SHSOperatingSystem = @{} + $_SHSOperatingSystem.Name = "_SHSOperatingSystem" + $_SHSOperatingSystem.Value = $OS + $Values += $_SHSOperatingSystem + } + + if ($ChassisType) { + if($ChassisType -match "Desktop" -or $ChassisType -match "SFF"){ + $ChassisGuid = 'c7f3c5ba-a8b3-40be-963f-7e1d75fb7fda' + } + elseif ($ChassisType -match "Micro") { + $ChassisGuid = '8d71a4c3-ae9d-4e55-98ea-2f8a75987cac' + } + elseif($ChassisType -match "Laptop"){ + $ChassisGuid = 'a8460b18-bab9-4c07-8fb1-a4cca4e63fa1' + } + elseif($ChassisType -match "Tablet"){ + $ChassisGuid = 'e6ae9cd4-9936-496d-8e5d-efce8417a3d0' + } + else{ + $ChassisGuid = '00000000-0000-0000-0000-000000000000' + } + $_SHSChassisType = @{} + $_SHSChassisType.Name = "_SHSChasisRef" + $_SHSChassisType.Value = $ChassisGuid + $Values += $_SHSChassisType + } + + if ($HDD) { + $_HardDiskSize = @{} + $_HardDiskSize.Name = "_HardDiskSize" + $_HardDiskSize.Value = $HDD + $Values += $_HardDiskSize + } + + if ($RAM) { + $_RAM = @{} + $_RAM.Name = "_RAM" + $_RAM.Value = $RAM + $Values += $_RAM + } + + if ($HDDAvail) { + $_AvailableDiskSpace = @{} + $_AvailableDiskSpace.Name = "_AvailableDiskSpace" + $_AvailableDiskSpace.Value = $HDDAvail + $Values += $_AvailableDiskSpace + } + + if ($IPAddress) { + $_IPAddress = @{} + $_IPAddress.Name = "_IPAddress" + $_IPAddress.Value = $IPAddress + $Values += $_IPAddress + } + + if ($Model) { + $_Model = @{} + $_Model.Name = "_Model" + $_Model.Value = $Model + $Values += $_Model + } + if ($SerialNumber) { + $_SerialNumber = @{} + $_SerialNumber.Name = "_SerialNumber" + $_SerialNumber.Value = $SerialNumber + $Values += $_SerialNumber + } + if ($Notes) { + $_SHSCINotes = @{} + $_SHSCINotes.Name = "_SHSCINotes" + $_SHSCINotes.Value = $Notes + $Values += $_SHSCINotes + } + # Look up table + if ($OSPatching) { + $Patch = Search-LANDeskOSPatchType -PatchType $OSPatching + $_SHSOSPatching = @{} + $_SHSOSPatching.Name = "_SHSOSPatching" + $_SHSOSPatching.Value = $Patch.Key + $Values += $_SHSOSPatching + } + #look up table + if ($PatchSchedule) { + $Schedule = Get-LANDeskPatchSchedule -PatchSchedule $PatchSchedule + $_SHSPatchSchedule = @{} + $_SHSPatchSchedule.Name = "_SHSPatchSchedule" + $_SHSPatchSchedule.Value = $Schedule + $Values += $_SHSPatchSchedule + } + if ($PatchNotes) { + $_SHSPatchNotes = @{} + $_SHSPatchNotes.Name = "_SHSPatchNotes" + $_SHSPatchNotes.Value = $PatchNotes + $Values += $_SHSPatchNotes + } + if ($LocationDetails){ + $LocationDetails + $_SHSLocation3 = @{} + $_SHSLocation3.Name = "_SHSLocation3" + $_SHSLocation3.Value = $LocationDetails + $Values += $_SHSLocation3 + } + + $body = [ordered]@{} + $body.class_name = '_CMDBManagement.Call' + $Body.originalValues = $Values + $body.formValues = $Values + $body = $body | ConvertTo-Json + + $uri = 'http://' + $server + '/' + $framework + '/api/form?class_name=_CMDBManagement.Call&key=' + $Key + '&function_name=Edit&v=*' + Invoke-RestMethod -Uri $uri -Headers $headers -Body $Body -Method Patch + + } + + end { + } +} \ No newline at end of file diff --git a/Private/Spark.ps1 b/Private/Spark.ps1 deleted file mode 100644 index 541d7d4..0000000 --- a/Private/Spark.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -$SparkHeaders = @{ - "Content-Type" = "application/json" - "Authorization" = "" - "Accept" = "*/*" - "Accept-Encoding" = "gzip, deflate, br" -} - -$SparkTenantPrefix = "samaritanhealth-amc" -$SparkURL = "https://$SparkTenantPrefix.ivanticloud.com" - -Function Connect-ISM { - try { - #Try a fast route to check if authorization headers are set properly - Invoke-RestMethod -Method Get -URI "$SparkURL/api/odata" -Headers $SparkHeaders - } catch { - $errobject = ConvertFrom-Json $_ - #A 404 means we were authorized and didn't find anything, as intended! - if ($errobject.code -eq "ISM_4004") { return } - #Anything other than a 401 Unauthorized is unexpected, attempt to handle gracefully - if ($errobject.code -ne "ISM_4001") { - Write-Host -ForegroundColor Red "Unexpected error connecting to Spark!" - Write-Host -ForegroundColor Red "$errobject" - $SparkHeaders["Authorization"] = "" - return - } - #Unuathorized response, so let's update our authorization! - if ( $SparkHeaders["Authorization"] ) { - Write-Host "Spark Authorization key expired, please update key" - } - $authKey = Read-Host "Login to Spark, open browser dev tools, and paste SID cookie here, or an API key if you have one" - if ($authKey -match "[0-9A-F](32)") { - $SparkHeaders["Authorization"] = "rest_api_key=$authKey" - } elseif ($authKey -match "$($SparkURL.split('/')[-1])#.*#") { - $SparkHeaders["Authorization"] = $authKey - } else { - Write-Host -ForegroundColor Yellow "Authorization key not a recognized key format" - $SparkHeaders["Authorization"] = "" - } - } -} - -if ($(Read-Host "Enable Spark features? (y/N)") -match "^y") { Connect-ISM } - -Function Get-SparkEnabled { - return -not -not $SparkHeaders["Authorization"] -} - -Function Get-SparkCI($CIName) { - Connect-ISM - $uri = "$SparkURL/api/odata/businessobject/CIs`?`$filter=Name+eq+%27$CIName%27" - try{ - $Query = Invoke-RestMethod -Method GET -uri $uri -headers $SparkHeaders - } catch { - $ExceptionErrors += $_.Exception.Message - } - return $Query.Value -} diff --git a/Private/Update-ISMBusinessObject.ps1 b/Private/Update-ISMBusinessObject.ps1 new file mode 100644 index 0000000..7fe5a82 --- /dev/null +++ b/Private/Update-ISMBusinessObject.ps1 @@ -0,0 +1,167 @@ +Function Update-ISMBusinessObject { + <# +.SYNOPSIS + Updates business object field values for specific records. +.DESCRIPTION + By providing the Business Object and the Record ID (RecID), the function will dynamically query the Business Object's metadata to provide a parameter list of all available fields for that particular business object. + + Additionally, use of the -RawJSONBody parameter allows a raw JSON payload to be passed directly if desired. +.PARAMETER -BusinessObject + The Business Object you are interacting with. (Incidents, CIs, CI__ActiveDirectorys, ServiceReqs, Locations, Employees, Manufacturers, StandardUserTeams, Problems, Changes, etc...) +.PARAMETER -RecID + The Record ID (RecID) of the specific record you wish to interact with. +.PARAMETER -RawJSONBody + Optional parameter for passing a raw JSON payload as opposed to letting the function dynamically query for the fields of the supplied business object and formatting the payload for you. +.PARAMETER -DetailedOutput + Optional parameter that will return the API URI and JSON Payload in addition to the standard output. +.INPUTS + Business Object, RecID and dynamic parameter values or a JSON payload. +.OUTPUTS + Returns the updated business object. +.NOTES + Version: 1.0 + Author: Sean Carlton + Creation Date: 08/16/2022 + Purpose/Change: Initial script development +.EXAMPLE + Update-ISMBusinessObject -BusinessObject cis -RecID "5788C7AB6E1E44AD8464FED18A510609" -Description "Important Description" -SHS_BusinessCriticality "Entity Essential" + + ======= + This example demonstrates updating the Description and SHS_BusinessCriticality fields on a CI record. + ======= +.EXAMPLE + $JSON_Payload = @" + { + "Description": "Test Description", + "SHS_BusinessCriticality": "Normal" + } + "@ + + Update-ISMBusinessObject -BusinessObject cis -RecID "5788C7AB6E1E44AD8464FED18A510609" -RawJSONBody $JSON_Payload + + ======= + This example demonstrates updating the Description and SHS_BusinessCriticality fields on a CI record by passing a raw JSON payload using the -RawJSONBody parameter. + ======= +.EXAMPLE + Update-ISMBusinessObject -BusinessObject employees -RecID "F922E4482A224E4FAE882D2B32AEBB6B" -DisplayName "John Smith" + + ======= + This example demonstrates updating the Display Name field on an employee record. + ======= + +#> + [CmdletBinding()] + param ( + [Alias("BO")] + [Parameter(HelpMessage="The business object you intend to update", + Mandatory=$true)] + [string] + $BusinessObject, + [Parameter(HelpMessage="The Record ID of the record you want to update", + Mandatory=$true)] + [string] + $RecID, + + [Parameter(HelpMessage="Optional parameter to pass a raw JSON Payload as opposed to leveraging the dynamic parameter query and generating the payload", + Mandatory=$false)] + [string] + $RawJSONBody, + + [Parameter(HelpMessage="Optional parameter ouput the JSON Payload that was generated/sent along with the standard output of the function", + Mandatory=$false, + DontShow)] + [switch] + $DetailedOutput, + + ### API Connection Parameters ### + [Parameter(HelpMessage = 'Tenant URL', DontShow)] + [string]$Tenant = (Connect-ISM)[1], + [Parameter(HelpMessage = 'Authorization Header', DontShow)] + [hashtable]$Headers = (Connect-ISM)[0] + ) + + dynamicparam + { + #Create a parameter dictionary. This object is ultimately leveraged to create a parameter for every field returned from the metadata. + $paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary + + $BO_Attributes = (Get-ISMBusinessObject -BusinessObject $BusinessObject -Metadata) + + if ($BO_Attributes.EntityType.count){ + $BO_Attributes = $BO_Attributes.EntityType[-1].Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name" + } else { + $BO_Attributes = $BO_Attributes.EntityType.Property | Where-Object {$_.Name -ne "RecID"} | Sort-Object "Name" + } + + Foreach ($Field in $BO_Attributes){ + # write-host "Parsing $($Field.name) with type $($Field.Type)" + $Attribute = New-Object System.Management.Automation.ParameterAttribute + + ##Account for any datatype name discrepancies between Powershell and ISM + switch ($Field.Type.split(".")[1]){ + "Date" {$AttributeType = "DateTime"} + "Boolean" {$AttributeType = "bool"} + "Byte" {$AttributeType = "Byte"} + "String" {$AttributeType = "String"} + default {$AttributeType = "String"} + } + + $dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($Field.Name, + $AttributeType, $Attribute) + + $paramDictionary.Add($Field.Name, $dynParam) + + }#End Foreach + + #Return the collection of dynamic parameters + $paramDictionary + + }#END DYNAMIC PARAMETER + + BEGIN { + + $URI = "$Tenant/api/odata/businessobject/$BusinessObject('$RecID')" + + If ($RawJSONBody){ + $Params = $RawJSONBody + } else { + $Params = $PSBoundParameters + $Params.Remove("BusinessObject") | Out-Null #//The Business Object is passed in the URI. + $Params.Remove("RecID") | Out-Null #//The RecID is passed in the URI. + $Params.Remove("DetailedOutput") | Out-Null #//Function-specific parameter. + $Params.Remove("RawJSONBody") | Out-Null #//Function-specific parameter. + $Params = $Params | ConvertTo-JSON + } + + try { + + $UpdatedBusObj = Invoke-RestMethod -Method PATCH -URI $uri -Headers $headers -Body $Params + + } + + catch { + write-warning "Failed updating the $BusinessObject Object with RecID $RecID!" + $_.ErrorDetails.Message + $_.Exception.Message + } + + } + + + END{ + + if ($DetailedOutput){ + + return $UpdatedBusObj, $Params, $uri + + } else { + + return $UpdatedBusObj + + } + + } + +}#END Update-ISMBusinessObject Function + +New-Alias -Name Update-ISMBO -Value Update-ISMBusinessObject -Force \ No newline at end of file diff --git a/Public/Get-PC.ps1 b/Public/Get-PC.ps1 index 884ba3c..ecd97c0 100644 --- a/Public/Get-PC.ps1 +++ b/Public/Get-PC.ps1 @@ -4,7 +4,7 @@ #region Module Import Block -$ErrorActionPreference = 'SilentlyContinue' +#$ErrorActionPreference = 'SilentlyContinue' #DevStage can take either Dev or Prod as values $devStage = 'Dev' #Locations for dev build and prod build @@ -124,6 +124,18 @@ Function Get-PC { [Switch]$Wake, [Switch]$WinProfileRebuild ) + + ## Define which Spark Record Properties are Returned + $Spark_Property_Return = @( + "Name", + "ivnt_assetfulltype", + "ivnt_location", + "SHS_LocationDetails", + "AssetTag", + "SerialNumber", + "Owner", + "Status" + ) if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Warning "Get-PC requires powershell to be run as administrator. Please re-launch powershell as administrator." @@ -137,8 +149,6 @@ Function Get-PC { return } - if ($EnableSpark) { Connect-ISM } - if ($PatchNotes) { $scriptparent = (get-item $PSScriptRoot ).parent.FullName Write-Host "`n" @@ -151,6 +161,53 @@ Function Get-PC { $outPutArray = Get-PCBatchJob($ComputerName) -printers Write-Output $outPutArray return + } + + if($locationSearch){ + Write-Host 'Please enter a partial location like a room number.' + $searchInput = Read-Host 'Location' + Write-Host "Searching CMDB for $searchInput ..." + <# $ComputerName + if($ComputerName.count -gt 1){ + Write-Host 'Can only search a single location at a time' -ForegroundColor Yellow + return + } #> + if($searchInput.ToCharArray().Length -lt 3){ + Write-Host 'Location searches much contain more than 2 characters' -ForegroundColor Yellow + return + } + + + $searchResults = Search-CMDB -Location $searchInput | Sort-Object -Property Hostname + if($searchResults -eq $null){ + Write-Host 'No results for given search' -ForegroundColor Yellow + return + } + + + Write-Output $searchResults + return + + } + + $charA = $ComputerName.ToCharArray() + if($charA -contains '*'){ + if($charA -lt 4){ + Write-Host "Wildcard searches need to be at least 4 characters long" -ForegroundColor Red + return + } + Write-Host "Starting CMDB Wildcard Search..." + # $searchResults = Search-CMDB -hostname $ComputerName.Replace('*','') | Sort-Object -Property Hostname + + + $searchResults = Find-ISMBO -BusinessObject "cis" -SearchQuery $ComputerName.Replace('*','') | Sort-Object -Property Name + + Write-Output $searchResults | Select-Object $Spark_Property_Return + if($TableView){ + $searchResults | Select-Object $Spark_Property_Return | Out-GridView -Title 'Get-PC Wildcard Search' + } + return + } $getPCComputers = @() #List of computers that will get a batch query