• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to footer
ControlUp Community

ControlUp Community

Connect, Learn, and Grow

  • Blog
  • Podcast
  • Meetups
  • Archives
  • Categories
    • ControlUp One Platform
    • ControlUp for Apps
    • ControlUp for Compliance
    • ControlUp for Desktops
    • ControlUp Scripts & Triggers
    • ControlUp Synthetic Monitoring
    • ControlUp for VDI
  • Topics
  • Events
    • Logos & Wallpaper
    • ControlUp.com
  • Join

ControlUp Script Batch Release – June 25, 2023

Posted on June 29, 2023

Written by: Bill Powell, Technical Integrations Manager at ControlUp

In case you hadn’t noticed, ControlUp is releasing batches of scripts every month, generally on the last Sunday of the month (with some variation to allow for holidays). The batch of scripts for June was released on 2023-06-25.

The scripts fell into four groups:

Group 1 – Citrix Licensing

  • Show Citrix CVAD License Details

Group 2 – Active Directory User Account Administration

  • Get AD User Account Expiration Date
  • Set AD User Account Expiration Date
  • Get AD User Forced to Change Password
  • Set AD User Force Change Password
  • Show Enable or Disable AD User Inactive Accounts

Group 3 – Windows Event filtering

  • Get Windows Events since Boot

Group 4 – Scripting using the new CUActions APIs

  • List Available CUActions
  • Power On Generic VM
  • Force Power Off Generic VM

For this post, I’d like to focus on the last group – working with the new CUActions APIs. They’re pretty self-explanatory, so you can use them as they are.

I’d like to go deeper, though, because when I was writing them, I thought a lot about making them examples of how to write scripts for the CUActions APIs. I hesitate to say they’re ‘Best Practice’ – as that’s something that we only learn over time. But they do contain techniques that are worth looking at.

First, let’s look at ‘List Available CUActions’. The full script is over at https://www.controlup.com/script-library-posts/list-available-cuactions/

As is conventional, the script starts with a Help Block, which mentions right up front that these scripts are designed to be executed on a CU Monitor server. This is because they rely on a DLL that’s only installed as part of the CU Monitor installation.

So here’s technique #1 – how to make sure you load the correct DLL. Why does that matter? Because the DLL works in conjunction with the Monitor service, and if you run the DLL against a different version of the monitor (yes, people do have multiple versions of the monitor on their servers) then you’re likely to see odd behaviour. ControlUp only tests the DLL against that CU Monitor that it is installed with.

#region Load the version of the module to match the running monitor and check that it has the new features

There are several DLLs shipped with the monitor – we only need to load one DLL in this case, but if more than one should be required, they can be added to the DLLList parameter.

#region Load the version of the module to match the running monitor and check that it has the new features

function Get-MonitorDLLs {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)][string[]]$DLLList
    )
    [int]$DLLsFound = 0
    Get-CimInstance -Query "SELECT * from win32_Service WHERE Name = 'cuMonitor' AND State = 'Running'" | ForEach-Object {
        $MonitorService = $_
        if ($MonitorService.PathName -match '^(?<op>"{0,1})\b(?<text>[^"]*)\1$') {
            $Path = $Matches.text
            $MonitorFolder = Split-Path -Path $Path -Parent
            $DLLList | ForEach-Object {
                $DLLBase = $_
                $DllPath = Join-Path -Path $MonitorFolder -ChildPath $DLLBase
                if (Test-Path -LiteralPath $DllPath) {
                    $DllPath
                    $DLLsFound++
                }
                else {
                    throw "DLL $DllPath not found in running monitor folder"
                }
            }
        }
    }
    if ($DLLsFound -ne $DLLList.Count) {
        throw "cuMonitor is not installed or not running"
    }
}

$AcceptableModules = New-Object System.Collections.ArrayList
try {
    $DllsToLoad = Get-MonitorDLLs -DLLList @('ControlUp.PowerShell.User.dll')
    $DllsToLoad | Import-Module 
    $DllsToLoad -replace "^.*\\",'' -replace "\.dll$",'' | ForEach-Object {$AcceptableModules.Add($_) | Out-Null}
}
catch {
    $exception = $_
    Write-Error "Required DLLs not loaded: $($exception.Exception.Message)"
}

if (-not ((Get-Command -Name 'Invoke-CUAction' -ErrorAction SilentlyContinue).Source) -in $AcceptableModules) {
   Write-Error "ControlUp version 8.8 commands are not available on this system"
   exit 0
}

#endregion

There are several DLLs shipped with the monitor – we only need to load one DLL in this case, but if more than one should be required, they can be added to the DLLList parameter.

Having found the required DLLs, the code then checks that ‘Invoke-CUAction’ has been loaded from one of the selected modules.

Running the script, you’ll see a list of actions that are available. Here’s an extract:

=======================================================================
Actions that operate on objects in the Sessions table
=======================================================================

Title                      Category                IsSBA Description                                                                                                                                                           
-----                      --------                ----- -----------                                                                                                                                                           
With user approval         Get Session Screenshot  False Retrieves the active user session desktop screenshot                                                                                                                  
With user notification     Get Session Screenshot  False Retrieves the active user session desktop screenshot                                                                                                                  
Without notifying the user Get Session Screenshot  False Retrieves the active user session desktop screenshot                                                                                                                  
Session                    Go to                   False Navigates to the process's session                                                                                                                                    
Kill Policy                Group Policy            False Removes explorer Group Policy on the selected session                                                                                                                 
Reapply Group Policy       Group Policy            False Reapplies the user's group policy removed by the 'Kill Policy' action.                                                                                                
Refresh User Group Policy  Group Policy            False Refreshes the user group policy using the command 'gpupdate.exe /target:user'                                                                                         
Start Process In Session   Processes               False Starts a new process in the selected session                                                                                                                          
Import Registry            Registry                False Imports a registry key from a file....                                                                                                                                
Disconnect Session         Remote Desktop Services False Disconnect a user session without notifing the user. If the selected target is an Account, then all the account sessions on the selected folders will be Disconnected.
LogOff Session             Remote Desktop Services False Logs off a user session without notifing the user. If the selected target is an Account, then all the account sessions on the selected folders will be logged off.    
Send Message               Remote Desktop Services False Sends a message to the selected sessions.                                                                                                                             
Send Super Message         Remote Desktop Services False Send a rich text message to the selected sessions, including graphics, text formatting and the ability to gain feedback from the user.                                


=======================================================================
Actions that operate on objects in the VMs table
=======================================================================

Title              Category            IsSBA Description                                                    
-----              --------            ----- -----------                                                    
Force Power Off VM VM Power Management False Forcefully powers off the virtual machine                      
Force Reset VM     VM Power Management False Forcefully resets the virtual machine                          
Power On VM        VM Power Management False Powers on the virtual machine on the hypervisor infrastructure.
Restart Guest      VM Power Management False Gracefully restarts the virtual machine                        
Shutdown Guest     VM Power Management False Gracefully shuts down the virtual machine                      

Two things to note.

  1. The Actions are broken down by table. Actions act upon entities (rows) within a table, and each entity has an identifier – a key – which tells the action which entity to act on.
  2. Every Action is marked with an ‘IsSBA’ setting. If IsSBA is false, the action is handled by logic in the main ControlUp product. This is useful where (for example) we don’t know which hypervisor manages a particular VM. So if we were writing a ‘Power On VM’ script prior to CUActions, we would have to include code to manage each hypervisor type. Instead, the ‘Power On VM’ action is handled by the core product, which already has code to distinguish different hypervisors.

Let’s move on to the pair of scripts ‘Power On Generic VM’ and ‘Force Power Off Generic VM’ – paired because it makes testing easier to have both the switch on and switch off scripts to hand.

The scripts make use of two CUActions shown in the output above, and are intended for use either as a right-click action or invoked from a trigger.

As part of the testing process, I needed to find some VMs that were currently powered off, that I could power on. I also needed to find some powered-on VMs – with no sessions – that I could safely power off. So I wrote a script for that, too, but I didn’t release it. If there’s interest, I will bring it up to production standard and put it in the July release.

Back to those two Generic VM scripts. Generic, because they will work against any hypervisor that ControlUp supports. Here’s the code:

#region Perform CUAction on target

$ThisComputer = Get-CimInstance -ClassName Win32_ComputerSystem

Write-Output "Action $RequiredAction applied to $hostname from $($ThisComputer.Name)"

$Action = (Get-CUAvailableActions -DisplayName $RequiredAction | Where-Object {($_.Title -eq $RequiredAction) -and ($_.IsSBA -eq $false) -and ($_.Category -eq $RequiredActionCategory)})[0]

$Allrows = Invoke-CUQuery -Table $Action.Table -Fields * -Where "sName = '${hostname}'"
$Allrows.Data | ForEach-Object {
    $VMTableRow = $_

    $Result = Invoke-CUAction -ActionId $Action.Id `
                              -Table $Action.Table `
                              -RecordsGuids @($VMTableRow.key)

    Write-Output "Action $RequiredAction applied to $hostname, result $($Result.Result)"
}

#endregion

The flow is identical for each of the scripts.

Step 1 – load the correct CUAction DLL, as above.

Step 2 – uniquely identify the CUAction by its name and category, yielding an Action object, which includes two fields that we will need for the Invoke-CUAction step:

Table: the name of the monitor table in which to search for the hostname

Id: the unique identifier that identifies the CUAction to be invoked

Step 3 – query the monitor table to find the row(s) identified by the VMs hostname. We’ll need the ‘key’ field from the row. There should only be a single VM for this use case. We’ll have a look at how to handle fetching multiple records in the next example.

Step 4 – invoke the CUAction. The action is asynchronous, so we don’t get a result back.

Job done.

Finally, let’s look at ‘Logoff Disconnected Sessions’. The idea is that we right click on an active session, and logoff all disconnected sessions for that user. The code is similar in most respects to the previous two scripts.

However, for this case, I assume that the user might have very many disconnected sessions, so I wrote code for extracting the records I’m interested in as a function (followed by the invocation). This is technique #2:

function Get-CUData {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)][string]$TableName,
        [Parameter(Mandatory=$false)][string]$Where,
        [Parameter(Mandatory=$false)][string[]]$FieldList,
        [Parameter(Mandatory=$false)][int]$ChunkSize = 100
    )
    if ($FieldList.Count -lt 1) {
        $FieldList = '*'
    }
    $ParamSplat = @{
        Table = $TableName;
        Fields = $FieldList;
        Take = $ChunkSize;
        Skip = 0;
    }
    if (-not [string]::IsNullOrWhiteSpace($Where)) {
        $ParamSplat["Where"] = $Where
    }
    do {
        $CUQueryResult = Invoke-CUQuery @ParamSplat
        $CUQueryResult.Data
        $ParamSplat["Skip"] += $chunkSize
    } while ($ParamSplat["Skip"] -lt $CUQueryResult.Total)
}

[System.Collections.Generic.List[object]]$SessionList = Get-CUData -TableName $Action.Table `
                                                        -Where "sUserAccount = '$UserAccount'"

We use the Take and Skip parameters to pull all the data from the table in chunks of no more than 100 records (configurable). After each iteration of the do/while we increment the offset into the result dataset by the chunksize, until there are no more records left to return.

We now iterate over the records returned, performing the logoff action for each session that is in disconnected state (4).

$SessionList | 
  Where-Object {$_.eConnectState -eq 4} | # only disconnected sessions, omit for all sessions
  ForEach-Object {
    $Session = $_
    Invoke-CUAction -ActionId $Action.Id `
                    -Table $Action.Table `
                    -RecordsGuids @($Session.key) | Out-Null
    Write-Output "Logoff session for user $($Session.sUserAccount) on host $($Session.sServerName)"
}

#endregion

View Bill’s entire post, follow the thread, and/or comment inside the ControlUp Community.

Not a Member? Join Today!


Categories: Blog, ControlUp Scripts & Triggers
Topics: Citrix, ControlUp Licensing, Logs, Microsoft, Microsoft Active Directory, Microsoft Windows, PowerShell, Scripts, Triggers

Ask Us Anything, Connect, Learn, and Grow with the ControlUp Community!

Login to the ControlUp Community to ask us anything, stay up-to-date on what’s new and coming soon and meet other like-minded techies like you.

Not already a member? Join Today!

Primary Sidebar

ControlUp Academy

Enroll in ControlUp Academy for expert-led technical training, equipping you with skills to effectively deploy, manage, and grow your ControlUp investment.

Learn here >

Rotating Images

Hidden Gem from our Community on Slack!

ControlUp Betas - What's Coming Next?
NEW ControlUp Features - Stay Up-to-Date!
ControlUp Scripts - Scripting, Zero to Hero
Latest KB Articles - Be the First to Learn
Did you Know - with Sivan Kroitoru
Practical Perspectives Technical Use Case Training

Video Tutorials Library

Visit our technical how-to videos, offering step-by-step tutorials on advanced features, troubleshooting, and best practices.

Watch here >

ControlUp Blog

Check out the ControlUp blog for expert advice and in-depth analysis.

Read here >

ControlUp Script Library

Visit the ControlUp technical script library, which offers a multitude of pre-built scripts and custom actions for your monitoring and troubleshooting requirements.

See here >

ControlUp Support

Visit the ControlUp support home and to delve deeper into ControlUp solutions.

Browse here >

Download ControlUp RealTime DX

Start with ControlUp for real-time end-user environment insights, swift troubleshooting, and unprecedented performance optimization. Download now.

Download here >

Footer

      

ControlUp Community
Of Techie, By Techie, For Techie!

Terms of Use | Privacy Policy | Security
Dive Deeper, Learn more at ControlUp.com

  • facebook
  • twitter
  • youtube
  • linkedin

© 2023–2025 ControlUp Technologies LTD, All Rights Reserved.

We use cookies to ensure that we give you the best experience on our website. by continuing to use this site you agree to our Cookie policy..