Public/Services/Add-GroupToSCManager.ps1

Function Add-GroupToSCManager {
    <#
        .Synopsis
            Adds a group to the Service Control Manager (SCM) ACL.

        .DESCRIPTION
            This function adds a specified group to the Service Control Manager (SCM) ACL with specified permissions.
            It modifies the SCM security descriptor to include the group with specific access rights.

        .EXAMPLE
            Add-GroupToSCManager -Group "SG_AdAdmins"

        .EXAMPLE
            Add-GroupToSCManager -Group "SG_AdAdmins" -computer DC1

        .EXAMPLE
            $Splat = @{
                Group = "SG_AdAdmins"
                Computer = DC1
                Verbose = $true
            }
            Add-GroupToSCManager @Splat

        .PARAMETER Group
            Specifies the group to be added to the SCM ACL.

        .PARAMETER Computer
            Remote computer to execute the commands.

        .PARAMETER Force
            If present, the function will not ask for confirmation when performing actions.

        .NOTES
            This function relies on SC.exe located at $env:SystemRoot\System32\

        .NOTES
            Used Functions:
                Name | Module
                ---------------------------------------|--------------------------
                Get-AdObjectType | EguibarIT.DelegationPS
                Write-Verbose | Microsoft.PowerShell.Utility
                Write-Error | Microsoft.PowerShell.Utility

        .NOTES
            Version: 1.1
            DateModified: 23/May/2025
            LasModifiedBy: Vicente Rodriguez Eguibar
                vicente@eguibar.com
                Eguibar IT
                http://www.eguibarit.com
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([void])]

    Param (
        # PARAM1 STRING for the Delegated Group Name
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Identity of the group getting the delegation, usually a DomainLocal group.',
            Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('IdentityReference', 'Identity', 'Trustee', 'GroupID')]
        $Group,

        # PARAM2 STRING for the Remote Computer Name
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Remote computer to execute the commands.',
            Position = 1)]
        [Alias('Host', 'PC', 'Server', 'HostName', 'ComputerName')]
        $Computer,

        # PARAM3 SWITCH to force operations without confirmation
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            HelpMessage = 'If present, the function will not ask for confirmation when performing actions.',
            Position = 2)]
        [Switch]
        $Force
    )

    Begin {

        Set-StrictMode -Version Latest

        $error.clear()

        # Display function header if variables exist
        if ($null -ne $Variables -and
            $null -ne $Variables.HeaderDelegation) {

            $txt = ($Variables.HeaderDelegation -f
                (Get-Date).ToString('dd/MMM/yyyy'),
                $MyInvocation.Mycommand,
                (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False)
            )
            Write-Verbose -Message $txt
        } #end if

        ##############################
        # Module imports

        ##############################
        # Variables Definition

        # Save current error action preference
        $savedErrorActionPreference = $ErrorActionPreference

        # Set to Continue to avoid terminating errors
        $ErrorActionPreference = 'Continue'

        # Verify Group exist and return it as Microsoft.ActiveDirectory.Management.AdGroup
        $CurrentGroup = Get-AdObjectType -Identity $PSBoundParameters['Group']

        # Get group SID
        $GroupSID = $CurrentGroup.SID.Value

        # Make sure computer has 'sc.exe'. sc.exe supports remoting by giving \\computername
        $ServiceControlCmd = Get-Command "$env:SystemRoot\system32\sc.exe"

        # Map permissions to access rights using enum values
        # https://learn.microsoft.com/en-us/windows/win32/services/service-security-and-access-rights
        $permissionMap = @{
            'FullControl'    = (
                [ServiceControlManagerFlags]::SC_MANAGER_ALL_ACCESS -bor
                [ServiceControlManagerFlags]::READ_CONTROL -bor
                [ServiceControlManagerFlags]::WRITE_DAC -bor
                [ServiceControlManagerFlags]::WRITE_OWNER -bor
                [ServiceControlManagerFlags]::DELETE
            )
            'ReadAndExecute' = (
                [ServiceControlManagerFlags]::SC_MANAGER_CONNECT -bor
                [ServiceControlManagerFlags]::SC_MANAGER_ENUMERATE_SERVICE -bor
                [ServiceControlManagerFlags]::SC_MANAGER_LOCK -bor
                [ServiceControlManagerFlags]::SC_MANAGER_QUERY_LOCK_STATUS -bor
                [ServiceControlManagerFlags]::READ_CONTROL -bor
                [ServiceControlManagerFlags]::Generic_Execute
            )
            'Read'           = (
                [ServiceControlManagerFlags]::SC_MANAGER_CONNECT -bor
                [ServiceControlManagerFlags]::SC_MANAGER_ENUMERATE_SERVICE -bor
                [ServiceControlManagerFlags]::READ_CONTROL
            )
            'Write'          = [ServiceControlManagerFlags]::SC_MANAGER_CREATE_SERVICE
            'Start'          = [ServiceControlManagerFlags]::SC_MANAGER_CONNECT
            'Stop'           = [ServiceControlManagerFlags]::SC_MANAGER_CONNECT
        }
    } #end Begin

    Process {
        # Get current 'Service Control Manager (SCM)' acl in SDDL format
        Write-Verbose -Message 'Get current "Service Control Manager (SCM)" acl in SDDL format'

        $MySDDL = if ($Computer) {
            (& $ServiceControlCmd.Definition @("\\$Computer", 'sdshow', 'scmanager'))[1]
        } else {
            (& $ServiceControlCmd.Definition @('sdshow', 'scmanager'))[1]
        } #end If-Else

        Write-Verbose -Message ('Retrieved SDDL: {0}' -f $MySDDL)

        # Build the Common Security Descriptor from SDDL
        Write-Verbose -Message 'Build the Common Security Descriptor from SDDL'
        $Permission = [System.Security.AccessControl.CommonSecurityDescriptor]::New($true, $False, $MySDDL)

        # Add new DACL
        Write-Verbose -Message 'Add new DACL'
        If ($Force -or $PSCmdlet.ShouldProcess($PSBoundParameters['Group'], 'Add group from SCM?')) {

            try {
                $Permission.DiscretionaryAcl.AddAccess(
                    [System.Security.AccessControl.AccessControlType]::Allow,
                    [System.Security.Principal.SecurityIdentifier]"$($GroupSID)",
                    $permissionMap['FullControl'], # int accessMask
                    [System.Security.AccessControl.InheritanceFlags]::None,
                    [System.Security.AccessControl.PropagationFlags]::None
                )

                $message = 'Successfully Added AccessControlType Allow for {0}'
                Write-Verbose -Message ($message -f $PSBoundParameters['Group'])
            } catch {
                $errorMessage = 'Failed to add access because {0}'
                Write-Error -Message ($errorMessage -f $_.Exception.Message)
            } #end Try-Catch

            # Commit changes
            Write-Verbose -Message 'Commit changes.'
            try {
                # Get SDDL
                Write-Verbose -Message 'Get SDDL from Common Security Descriptor.'
                $accessControlSections = [System.Security.AccessControl.AccessControlSections]::All
                $sddl = $Permission.GetSddlForm($accessControlSections)

                # Use sc.exe to set the SDDL directly
                # This approach avoids using Set-Acl which requires administrative privileges
                If ($Computer) {
                    $scArgs = @("\\$Computer", 'sdset', 'scmanager', "$sddl")
                    $result = & $ServiceControlCmd.Definition $scArgs
                } else {
                    $scArgs = @('sdset', 'scmanager', "$sddl")
                    $result = & $ServiceControlCmd.Definition $scArgs
                }

                # Check if the operation was successful
                if ($LASTEXITCODE -eq 0 -or $result -match 'SUCCESS') {
                    Write-Verbose -Message 'Successfully set ACL in Service Control Manager'
                } else {
                    $failMessage = 'Failed to set Security in the Service Control Manager: {0}'
                    Write-Error -Message ($failMessage -f $result)
                }
            } catch {
                $failExMessage = 'Failed to set Security in the Service Control Manager because {0}'
                Write-Error -Message ($failExMessage -f $_.Exception.Message)
            } #end Try-Catch
        } #end If

    } #end Process

    End {
        # Restore previous error action preference
        $ErrorActionPreference = $savedErrorActionPreference

        # Display function footer if variables exist
        if ($null -ne $Variables -and
            $null -ne $Variables.FooterDelegation) {

            $txt = ($Variables.FooterDelegation -f $MyInvocation.InvocationName,
                'adding Service Control Manager (SCM) access.'
            )
            Write-Verbose -Message $txt
        } #end if
    } #end End
} #end Function Add-GroupToSCManager