With the 8 commands mastered, let’s look at some Service Manager PowerShell functions you can take and begin using in your environment immediately.
Add-ActionLogEntry
Having retrieved a Work Item object via Get-SCSMObject, you can use this function to add an Action Log entry to it. What makes this function so useful is that it can handle Incidents, Service Requests, Problems, and Change Requests. Believe it or not all of these Work Item classes have ever so slightly different requirements about how to work with their Action Log. Plus – Action Logs are more than just Comments but also actions you can take against these Work Items such as Assigning or Reactivating.
Additionally, unlike how parameters have been previously declared in the series by simply putting a variable in parentheses, here there is far more rigor applied to them by placing them into their own dedicated parameter (i.e. param) block. This provides the distinct advantage of introducing logic/requirements around how the values to those parameters are declared. It also means you can form a PowerShell editor tab through the values you are allowed to use for the Action you want to take. No guessing or memorization required!
function Add-ActionLogEntry {
param (
[parameter(Mandatory=$true, Position=0)]
$WIObject,
[parameter(Mandatory=$true, Position=1)]
[ValidateSet("Assign","AnalystComment","Closed","Escalated","EmailSent","EndUserComment","FileAttached","FileDeleted","Reactivate","Resolved","TemplateApplied")]
[string] $Action,
[parameter(Mandatory=$true, Position=2)]
[string] $Comment,
[parameter(Mandatory=$true, Position=3)]
[string] $EnteredBy,
[parameter(Mandatory=$false, Position=4)]
[Nullable[boolean]] $IsPrivate = $false
)
#Choose the Action Log Entry to be created. Depending on the Action Log being used, the $propDescriptionComment Property could be either Comment or Description
switch ($Action)
{
Assign {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.RecordAssigned"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
AnalystComment {$CommentClass = "System.WorkItem.TroubleTicket.AnalystCommentLog"; $propDescriptionComment = "Comment"}
Closed {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.RecordClosed"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
Escalated {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.RecordEscalated"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
EmailSent {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.EmailSent"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
EndUserComment {$CommentClass = "System.WorkItem.TroubleTicket.UserCommentLog"; $propDescriptionComment = "Comment"}
FileAttached {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.FileAttached"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
FileDeleted {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.FileDeleted"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
Reactivate {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.RecordReopened"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
Resolved {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.RecordResolved"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
TemplateApplied {$CommentClass = "System.WorkItem.TroubleTicket.ActionLog"; $ActionType = "System.WorkItem.ActionLogEnum.TemplateApplied"; $ActionEnum = get-scsmenumeration $ActionType; $propDescriptionComment = "Description"}
}
#Alias on Type Projection for Service Requests and Problem and are singular, whereas Incident and Change Request are plural. Update $CommentClassName
if (($WIObject.ClassName -eq "System.WorkItem.Problem") -or ($WIObject.ClassName -eq "System.WorkItem.ServiceRequest")) {$CommentClassName = "ActionLog"} else {$CommentClassName = "ActionLogs"}
#Analyst and End User Comments Classes have different Names based on the Work Item class
if ($Action -eq "AnalystComment")
{
switch ($WIObject.ClassName)
{
"System.WorkItem.Incident" {$CommentClassName = "AnalystComments"}
"System.WorkItem.ServiceRequest" {$CommentClassName = "AnalystCommentLog"}
"System.WorkItem.Problem" {$CommentClassName = "Comment"}
"System.WorkItem.ChangeRequest" {$CommentClassName = "AnalystComments"}
}
}
if ($Action -eq "EndUserComment")
{
switch ($WIObject.ClassName)
{
"System.WorkItem.Incident" {$CommentClassName = "UserComments"}
"System.WorkItem.ServiceRequest" {$CommentClassName = "EndUserCommentLog"}
"System.WorkItem.Problem" {$CommentClass = "System.WorkItem.TroubleTicket.AnalystCommentLog"; $CommentClassName = "Comment"}
"System.WorkItem.ChangeRequest" {$CommentClassName = "UserComments"}
}
}
# Generate a new GUID for the Action Log entry
$NewGUID = ([guid]::NewGuid()).ToString()
# Create the object projection with properties
$Projection = @{__CLASS = "$($WIObject.ClassName)";
__SEED = $WIObject;
$CommentClassName = @{__CLASS = $CommentClass;
__OBJECT = @{Id = $NewGUID;
DisplayName = $NewGUID;
ActionType = $ActionType;
$propDescriptionComment = $Comment;
Title = "$($ActionEnum.DisplayName)";
EnteredBy = $EnteredBy;
EnteredDate = (Get-Date).ToUniversalTime();
IsPrivate = $IsPrivate;
}
}
}
#Create the projection based on the work item class
switch ($WIObject.ClassName)
{
"System.WorkItem.Incident" {New-SCSMObjectProjection -Type "System.WorkItem.IncidentPortalProjection$" -Projection $Projection}
"System.WorkItem.ServiceRequest" {New-SCSMObjectProjection -Type "System.WorkItem.ServiceRequestProjection$" -Projection $Projection}
"System.WorkItem.Problem" {New-SCSMObjectProjection -Type "System.WorkItem.Problem.ProjectionType$" -Projection $Projection}
"System.WorkItem.ChangeRequest" {New-SCSMObjectProjection -Type "Cireson.ChangeRequest.ViewModel$" -Projection $Projection}
}
}
#Depending on the action you want to take against the Work Item, call the function
Add-ActionLogEntry -WIObject $incidentObject -Action "AnalystComment" -Comment "Still haven't heard anything back from the Affected User" -EnteredBy "Adam" -IsPrivate $true
Add-ActionLogEntry -WIObject $serviceRequestObject -Action "Assign"
Get-WorkItemsByDepartment
Since SCSM syncs Active Directory users and their various attributes. I always wanted a quick way to say “Show me all of the Work Items for a specific department.” This function simply requires a Department name as seen on an Active Directory User, a management server name so you can run it remotely, and does the rest for you. I’ve also included two optional ways you could call the function.
function Get-WorkItemsByDepartment ($ADDepartment, $ComputerName)
{
#Declare classes to work with
$userClass = Get-SCSMClass -name "System.Domain.User$" -computername $ComputerName
$affectedUserRelClass = Get-SCSMRelationshipClass -Name "System.WorkItemAffectedUser$" -ComputerName $ComputerName
#Get all of the employees in the department
$employees = Get-SCSMObject -class $userClass -Filter "Department -eq '$ADDepartment'" -computername $ComputerName
#Create an empty array to load data into as we process through the loop
$activeWork = @()
foreach ($employee in $employees)
{
#Get Work Items where the Employee is the Affected User
$affectedUserWorkItems = Get-SCSMRelationshipObject -TargetObject $employee -TargetRelationship $affectedUserRelClass -computername $ComputerName
#For each of the Work Items for that single employee, get their Work Items, then add them to the array
$activeWork += $affectedUserWorkItems | Foreach-Object {Get-SCSMObject -id $_.SourceObject.Id -computername $ComputerName}}
}
#Once we've loaded the array with the Work Items for each employee in the Department, ensure the function outputs it with the return command
return $activeWork
}
#Call the function
Get-WorkItemsByDepartment -ADDepartment "Human Resources" -computername "mgmtserver01"
#Get a bit more dynamic by throwing the dept name into a variable, performing some distillation with Select-Object, then throw it out to a CSV on the user's desktop who is currently running this with a filename based on the department it was run for
$department = "Human Resources"
Get-WorkItemsByDepartment -ADDepartment $department -computername "mgmtserver01" | Select-Object CreatedDate, Status, Name, Title, Description | Export-CSV "C:\users\$env:username\desktop\$department - Work Items.csv"
New-ServerIncident
I want a quick way to generate an Incident that was about a server. This function takes the Incident’s Title, Description, the computer the Incident is about, and finally the management server name so you can run it remotely. Lastly, it takes a Support Group value in plain text and performs the enum lookup for you. Why make enums harder than they need to be, right?
function New-ServerIncident ($Title, $Description, $SupportGroupName, $ServerName, $ComputerName)
{
$irClass = Get-SCSMClass -Name "System.WorkItem.Incident$" -ComputerName $ComputerName
$compClass = Get-SCSMClass -Name "Microsoft.Windows.Computer$" -ComputerName $ComputerName
$wiAboutConfigItemRelClass = Get-SCSMRelationshipClass -name "System.WorkItemAboutConfigItem$" -ComputerName $ComputerName
$server = Get-SCSMObject -Class $compClass -Filter "DisplayName -eq '$ServerName'" -ComputerName $ComputerName
$supportGroup = Get-SCSMEnumeration -Name "IncidentTierQueue" | Get-SCSMChildEnumeration | Where-Object {$_.DisplayName -eq $SupportGroupName}
$properties = @{
"ID" = "IR{0}";
"Title" = $Title
"Description" = $Description
"TierQueue" = $supportGroup
}
#create the Incident
$newIncident = New-SCSMObject -Class $irClass -PropertyHashtable $properties -ComputerName $ComputerName -PassThru
#relate the server to the recently created Incident
New-SCSMRelationshipObject -Source $newIncident -Relationship $wiAboutConfigItemRelClass -Target $server -ComputerName $ComputerName -bulk
#show the Incident
return $newIncident
Add-SCSMReviewer
When it comes to working with Review Activities in Service Manager, they aren’t as straightforward as an Incident’s Assigned To or a Manual Activities Implementer. The reason being is that a Review Activity has potentially many Reviewers. What’s more, to work with Users on Review Activities it’s two relationships away. Unlike an Incident that correlates straight to a single Affected user, a Review Activity relates to a Reviewer and that Reviewer relates to a User/Group. Which means it requires just a little bit more effort to work with them.
You’ve seen this before anytime you’ve found a Review Activity with an empty Reviewer. This function takes a Review Activity ID, an User Object (of the System.Domain.User class), and as always a computername parameter so you can run this remotely. For the sake of a complete example, I’m retrieving a single user object before the function is invoked. The other reason is that because it means you could substitute a user or users and pass them into the function. Again – by keeping a PowerShell function vague it’s easy to share across environments.
function Add-SCSMReviewer ($ReviewActivityID, $SCSMUser, $Computername)
{
#Get the classes we need to work with
$raClass = Get-SCSMClass -name "System.WorkItem.Activity.ReviewActivity$" -ComputerName $Computername
$reviewerClass = Get-SCSMClass -name "System.Reviewer$" -ComputerName $Computername
$raHasReviewerRelClass = Get-SCSMRelationshipClass "System.ReviewActivityHasReviewer$" -ComputerName $Computername
$raReviewerIsUserRelClass = Get-SCSMRelationshipClass "System.ReviewerIsUser$" -ComputerName $Computername
#Get the Review Activity to update
$ra = Get-SCSMObject -class $raClass -filter "Name -eq '$ReviewActivityID'" -ComputerName $Computername
#Loop through the incoming array/list of users
foreach ($user in $SCSMUser)
{
#Define an empty Reviewer object and their voting options in memory only by declaring the "-NoCommit" switch
$reviewer = New-SCSMObject -Class $reviewerClass -PropertyHashtable @{"ReviewerID" = "{0}"; "Veto" = $false; "MustVote" = $false} -ComputerName $Computername -NoCommit
#Relate the empty Reviewer to the Review Activity in memory by declaring the "-NoCommit" switch
$RAhasReviewer = New-SCSMRelationshipObject -Source $ra -Relationship $raHasReviewerRelClass -Target $reviewer -ComputerName $Computername -NoCommit
#Relate the empty Reviewer object to a User in memory by declaring the "-NoCommit" switch
$reviewerHasUser = New-SCSMRelationshipObject -Source $reviewer -Relationship $raReviewerIsUserRelClass -Target $user -ComputerName $Computername -NoCommit
#With everything defined in memory, finally commit/save all of those items in one go to Service Manager
$RAhasReviewer.Commit()
$reviewerHasUser.Commit()
}
}
$userClass = Get-SCSMClass -name "System.Domain.User$"
$user = Get-SCSMObject -filter "Username -eq 'Adam'"
Add-SCSMReviewer -ReviewActivityID "RA957402" -SCSMUser $user
Get-BusinessServiceUsers
Distributed Applications in Operations Manager are certainly the icing on the cake for Alerting and parent level health concepts. But in the world of Service Manager they can mean so much more as those Distributed Applications float into Service Manager as Business Services. With those Business Services in Service Manager, we’re able to define things like Service Contacts, Service Customers, and Affected Users. Which ultimately leads us into the ITSM Endgame of raising Incidents that automatically contact those individuals or Change Requests that request their approval.
Regardless of the Work Item type, we first need to get those Users from the Business Service. This function takes a Business Service name and returns SCSM User objects as unique properties which you could then use to email, randomly select, add to Review Activities, or any other number of scenarios you can come up with.
function Get-BusinessServiceUsers ($ServiceName, $Computername)
{
#Define the classes/objects to work with
$serviceClass = Get-SCSMClass "System.Service$" -computername $Computername
$service = Get-SCSMObject -Class $serviceClass -filter "DisplayName -eq '$ServiceName'" -computername $Computername
#Filter based on the Business Service/User Relationship, then only retrieve the Target Objects (Users)
$ServiceOwner = (Get-SCSMRelationshipObject -BySource $service -filter "RelationshipId -eq 'cbb45424-b0a2-72f0-d535-541941cdf8e1'" -computername $Computername).TargetObject
$ServiceContacts = (Get-SCSMRelationshipObject -BySource $service -filter "RelationshipId -eq 'dd01fc9b-20ce-ea03-3ec1-f52b3241b033'" -computername $Computername).TargetObject
$ServiceCustomers = (Get-SCSMRelationshipObject -BySource $service -filter "RelationshipId -eq '3c00f0fa-66e5-642a-e24d-93fecc6b4f6d'" -computername $Computername).TargetObject
$AffectedUsers = (Get-SCSMRelationshipObject -BySource $service -filter "RelationshipId -eq 'fbd04ee6-9de3-cc91-b9c5-1807e303b1cc'" -computername $Computername).TargetObject
#Create a custom PowerShell object to independently store users of different relationships to a more distilled data model
$users = New-Object -TypeName psobject
$users | Add-Member -MemberType NoteProperty -Name ServiceOwner -Value $ServiceOwner
$users | Add-Member -MemberType NoteProperty -Name ServiceContacts -Value $ServiceContacts
$users | Add-Member -MemberType NoteProperty -Name ServiceCustomers -Value $ServiceCustomers
$users | Add-Member -MemberType NoteProperty -Name AffectedUsers -Value $AffectedUsers
return $users
}
#Call the function
$serviceUsers = Get-BusinessServiceUsers -servicename "Networking" -computername "localhost"
#Show all the users
$serviceUsers
#Show just one subset of the users
$serviceUsers.ServiceContacts
Get-SCSMUserByEmailAddress
You may be surprised to discover that an email address on an employee in Service Manager isn’t a property, but in fact a relationship. This function takes an email address and gives you a single SCSM Active Directory User right back.
function Get-SCSMUserByEmailAddress ($EmailAddress, $ComputerName)
{
$notificationClass = Get-SCSMClass -name "System.Notification.Endpoint$" -ComputerName $ComputerName
$userSMTPNotification = Get-SCSMObject -Class $notificationClass -Filter "TargetAddress -eq '$EmailAddress'" -computername $ComputerName | sort-object lastmodified -Descending | select-object -first 1
if ($userSMTPNotification)
{
$user = Get-SCSMobject -id (Get-SCSMRelationshipObject -ByTarget $userSMTPNotification -computername $ComputerName).sourceObject.id -computername $ComputerName
return $user
}
else
{
return $null
}
}
Get-HWAssetsInLocation
It’s just what it sounds like! Enter a known Cireson Asset Management Location and return all of the Hardware Assets in it.
function Get-HWAssetsInLocation ($LocationName, $ComputerName)
{
$locationClass = Get-SCSMClass -name "Cireson.AssetManagement.Location$" -computername $ComputerName
$hwAssetHasLocationRelClass = Get-SCSMRelationshipClass -Name "Cireson.AssetManagement.HardwareAssetHasLocation" -ComputerName $ComputerName
$location = Get-SCSMObject -Class $scsmLocationClass -Filter "Name -eq '$LocationName'" -ComputerName $ComputerName
$hwAssets = Get-SCSMRelationshipObject -ByTarget $location -ComputerName $ComputerName | where-object {$_.relationshipid -eq $hwAssetHasLocationRelClass.Id} | select-object sourceobject -expandproperty sourceobject
return $hwAssets
}
It’s Time to Wrap this Up!