Create Solve users from AD Security Group

This script is intended to be used to sync the Solve users, based on the membership of the specified AD Security group.
Version 2.3.7
Created on 2023-09-06
Modified on 2023-10-26
Created by Bill Powell
Downloads: 40

The Script Copy Script Copied to clipboard
#requires -Modules ActiveDirectory

<#
  .SYNOPSIS
  This script synchronises AD users from an AD Security Group to Solve 
  .DESCRIPTION
  This script extracts AD users from an AD Security Group. It checks whether the User Principal Name is already registered with Solve (using Get-CUUsersList), and if not, performs the registration using Add-CUUser
  .PARAMETER SolveUserSecurityGroup
  The AD Security Group to perform the action on, supplied via a trigger
  .NOTES
   Version:        0.1
   Context:        Computer, executes on Monitor
   Author:         Bill Powell
   Requires:       Realtime DX 8.8, ActiveDirectory module (RSAT)
   Creation Date:  2023-09-06

  .LINK
   https://support.controlup.com/docs/powershell-cmdlets-for-solve-actions
#>


[CmdletBinding()]
param (
    [Parameter(Mandatory = $false, HelpMessage = 'Active Directory Security Group defining Solve users to be created')]
    [ValidateNotNullOrEmpty()]
    [string]$SolveUserSecurityGroup
)

Import-Module ActiveDirectory

# uncomment for CU Internal Testing
#$SolveUserSecurityGroup = 'sg-SolveUsersTest'

#region ControlUpScriptingStandards
$VerbosePreference = $(if( $PSBoundParameters[ 'verbose' ] ) { $VerbosePreference } else { 'SilentlyContinue' })
$DebugPreference = $(if( $PSBoundParameters[ 'debug' ] ) { $DebugPreference } else { 'SilentlyContinue' })
$ErrorActionPreference = $(if( $PSBoundParameters[ 'erroraction' ] ) { $ErrorActionPreference } else { 'Stop' })
$ProgressPreference = 'SilentlyContinue'

[int]$outputWidth = 400
if( ( $PSWindow = (Get-Host).UI.RawUI ) -and ( $WideDimensions = $PSWindow.BufferSize ) )
{
    $WideDimensions.Width = $outputWidth
    $PSWindow.BufferSize = $WideDimensions
}
#endregion ControlUpScriptingStandards

#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.Generic.List[object]
try {
    $DllsToLoad = Get-MonitorDLLs -DLLList @('ControlUp.PowerShell.User.dll')
    $DllsToLoad | Import-Module 
    $DllsToLoad -replace "^.*\\",'' -replace "\.dll$",'' | ForEach-Object {$AcceptableModules.Add($_)}
}
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

$ADPropertyList = 'accountExpires,SamAccountName,UserPrincipalName,DistinguishedName,mail,GivenName,Surname' -split ','

#region expand group

[hashtable]$Script:DNsProcessed = @{}    # <- every DN goes into here as it is processed, to prevent
                                         # duplication and infinite recursion
$Script:AccountsInGroup = New-Object System.Collections.Generic.List[PSObject]

function Unpack-NestedGroup {
    [CmdletBinding()]
    param (
        [string]$GroupDN
    )
    if ($Script:DNsProcessed[$GroupDN] -eq $true) {
        return
    }
    $Script:DNsProcessed[$GroupDN] = $true
    $ADGroup = Get-ADGroup -Identity $GroupDN -Properties members
    $ADGroup.members | ForEach-Object {
        $memberDN = $_
        $ADObject = Get-ADObject -Identity $memberDN
        switch ($ADObject.ObjectClass) {
            'group' {
                    Unpack-NestedGroup -GroupDN $memberDN
                }
            'user' {
                    if ($Script:DNsProcessed[$memberDN] -ne $true) {
                        $Script:AccountsInGroup.Add($memberDN)
                        $Script:DNsProcessed[$memberDN] = $true
                    }
                }
            default {}
        }
    }
}

#endregion

$GroupToAdd = Get-ADGroup -Filter "name -eq '$SolveUserSecurityGroup'"

if ($null -ne $GroupToAdd) {
    #
    Unpack-NestedGroup -GroupDN $GroupToAdd.DistinguishedName
    # getting the CU Users list is slow - so no point if we haven't found the group
    $CUUsers = Get-CUUsersList

    $Script:AccountsInGroup | ForEach-Object {
        $DistinguishedName = $_
        $ADUser = Get-ADUser -Identity $DistinguishedName -Properties $ADPropertyList
        <#
        # for CU internal test
        if ($ADUser.accountExpires -lt ((Get-Date).ToFileTime())) {
            #
            # account has expired - extend expiration date by 1 month
            $NewExpiry = (Get-Date).AddMonths(1)
            Set-ADUser -Identity $ADUser.SamAccountName -AccountExpirationDate $NewExpiry
        }
        $ADUser | Out-Null
        #>
        $SolveUser = $CUUsers | Where-Object {$_.upn -eq $ADUser.UserPrincipalName}
        if ($SolveUser -eq $null) {
            #
            # not currently a solve user - let's create them as such
            $UserDnsDomain = (($ADUser.DistinguishedName -split ',' | Where-Object {$_ -like "DC=*"}) -replace "^DC=",'') -join '.'
            $AddUserSplat = @{
                Upn = $ADUser.UserPrincipalName
                Email = $ADUser.mail
                SamAccountName = $ADUser.SamAccountName
                UserDnsDomain = $UserDnsDomain
                FirstName = $ADUser.GivenName
                LastName = $ADUser.Surname
            }
            Add-CUUser @AddUserSplat
            Write-Output "Group: $SolveUserSecurityGroup , user $($ADUser.UserPrincipalName) has been added as a solve user"
        }
        else {
            Write-Output "Group: $SolveUserSecurityGroup , user $($ADUser.UserPrincipalName) is already a solve user"
        }
    }
}
else {
    Write-Error "AD group $SolveUserSecurityGroup not found"
    exit 1
}