SvcAce.psm1

function Get-SvcSddl {
    <#
    .SYNOPSIS
        Gets the access control list of a service in SDDL form.
     
    .DESCRIPTION
        Get-SvcSddl is a function that gets the access control list SDDL form for a specific service on a local or remote machine.
 
    .PARAMETER ComputerName
        Specifies the name of the machine to execute this script on.
     
    .PARAMETER ServiceName
        Specifies the shortname of the service.
 
    .EXAMPLE
        Get-SvcSddl -ComputerName 'server1' -ServiceName 'service1'
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Position=0,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq $ENV:COMPUTERNAME}{
                    $true
                }
                {$_ -ne $ENV:COMPUTERNAME} {
                    if (Test-WSMan -ComputerName $_ -ErrorAction SilentlyContinue) {
                        $true
                    }
                    else {
                        $false
                        throw "Can't connect to remote computer $_ .."
                    }
                }
            }
        })]
        [string]$ComputerName,
        [Parameter(
            Position=1,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq 'scmanager'}{
                    $true
                    break
                }
                {$ComputerName -eq $ENV:COMPUTERNAME} {
                    if ($_ -eq (Get-Service -Name $_ -ErrorAction SilentlyContinue).Name) {
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
                {$ComputerName -ne $ENV:COMPUTERNAME} {
                    if ($_ -eq (Invoke-Command -ComputerName $ComputerName -ScriptBlock{Get-Service -Name $using:_ -ErrorAction SilentlyContinue})){
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
            }
        })]
        [string]$ServiceName
    )

    ### Checking if code is running in elevated session
    $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($id)
    if (!($Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))){
        Throw "Script is not running as Administrator. Stopping script, no changes were made .."
    }  

    ### Getting SDDL
    switch ($ComputerName) {
        {@($_ -ne $ENV:COMPUTERNAME)} {
                $sddl = Invoke-Command -ScriptBlock {sc.exe sdshow $using:ServiceName | Where-Object {$_}} -ComputerName $_ -ErrorAction Stop
        }
        {@($_ -eq $ENV:COMPUTERNAME)} {
                $sddl = sc.exe sdshow $ServiceName | Where-Object {$_} -ErrorAction Stop
        }
    }
    ### Print output
    $sddl | Out-String | ConvertFrom-String -PropertyNames SDDL | Format-Table -Wrap
}
function Get-SvcAce {
    <#
    .SYNOPSIS
        Get all access control entries for a service that contains the entered sid.
     
    .DESCRIPTION
        Get-SvcAce is a function that gets the access control entry for a service which contains the entered sid.
 
    .PARAMETER ComputerName
        Specifies the name of the machine to execute this script on.
     
    .PARAMETER ServiceName
        Specifies the shortname of the service.
 
    .PARAMETER sid
        Specifies the SID of an identity, eg. user or group.
     
    .EXAMPLE
        Get-SvcAce -ComputerName 'server1' -ServiceName 'service1' -sid 'S-1-5-18'
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Position=0,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq $ENV:COMPUTERNAME}{
                    $true
                }
                {$_ -ne $ENV:COMPUTERNAME} {
                    if (Test-WSMan -ComputerName $_ -ErrorAction SilentlyContinue) {
                        $true
                    }
                    else {
                        $false
                        throw "Can't connect to remote computer $_ .."
                    }
                }
            }
        })]
        [string]$ComputerName,
        [Parameter(
            Position=1,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq 'scmanager'}{
                    $true
                    break
                }
                {$ComputerName -eq $ENV:COMPUTERNAME} {
                    if ($_ -eq (Get-Service -Name $_ -ErrorAction SilentlyContinue).Name) {
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
                {$ComputerName -ne $ENV:COMPUTERNAME} {
                    if ($_ -eq (Invoke-Command -ComputerName $ComputerName -ScriptBlock{Get-Service -Name $using:_ -ErrorAction SilentlyContinue})){
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
            }
        })]
        [string]$ServiceName,
        [Parameter(
            Position=2,
            Mandatory=$false
        )]
        [ValidateScript({
            $SidTokens = @(
            "DA","DG","DU","ED","DD","DC","BA","BG","BU","LA","LG","AO","BO","PO","SO","AU","PS","CO","CG","SY","PU","WD","RE","IU","NU","SU","RC",
            "WR","AN","SA","CA","RS","EA","PA","RU","LS","NS","RD","NO","MU","LU","IS","CY","OW","ER","RO","CD","AC","RA","ES","MS","UD","HA","CN",
            "AA","RM","LW","ME","MP","HI","SI"
            )
            if ($SidTokens -contains $_) {
                $false
                throw "$_ is a sid-token. An abbreviated form of a well-known SID, and should not be modified."
            }
            else {
                $true
            }
        })]
        [ValidateNotNullOrEmpty()]
        [string]$sid = $ENV:SvcAceSID
    )

    ### Checking if code is running in elevated session
    $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($id)
    if (!($Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))){
        Throw "Script is not running as Administrator. Stopping script, no changes were made .."
    }  

    ### Creating security identifier for sid
    $sid = New-Object System.Security.Principal.SecurityIdentifier($sid)

    ### Getting SDDL
    switch ($ComputerName) {
        {@($_ -ne $ENV:COMPUTERNAME)} {
                $sddl = Invoke-Command -ScriptBlock {sc.exe sdshow $using:ServiceName | Where-Object {$_}} -ComputerName $_ -ErrorAction Stop
        }
        {@($_ -eq $ENV:COMPUTERNAME)} {
                $sddl = sc.exe sdshow $ServiceName | Where-Object {$_} -ErrorAction Stop
        }
    }

    ### Converting SDDL to RawSD
    $RawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)

    ### Checking if SID has an entry
    if ($RawSD.DiscretionaryAcl.SecurityIdentifier -contains $sid){
        foreach ($a in $RawSD.DiscretionaryAcl){
            if ($a.SecurityIdentifier.Value -eq $sid) {
                $a
            }
        }
    }    
}
function New-SvcAce {

    <#
    .SYNOPSIS
        Configures the security descriptor string of a specified service to add an
        access control entry with the permissions specified.
     
    .DESCRIPTION
        New-SvcAce is a function that modifies the security descriptor on a defined service,
        by adding an Ace that grants a SID, eg. group or user, access as specified.
 
    .PARAMETER ComputerName
        Specifies the name of the machine to execute this script on.
     
    .PARAMETER ServiceName
        Specifies the shortname of the service that will be configured with a new Ace with
        the sid and accessmask.
 
    .PARAMETER sid
        Specifies the SID of an identity, eg. user or group that the access will be granted to.
     
    .PARAMETER accessMask
        Specifies the access mask in HEX that translates into the permissions that is granted in the ACE.
        If no value is supplied, the default accessMask will be "0x2009D" which is HEX for the permissions "CCLCSWRPLORC",
        which is the basic read rights to poll services for data to be used for monitoring.
        If scmanager is defined as ServiceName, the accessmask will be corrected to "0x2001D" as that is what is supported for scmanager.
        See more information for permissions: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f4296d69-1c0f-491f-9587-a960b292d070
 
    .EXAMPLE
        New-SvcAce -ComputerName 'server1' -ServiceName 'service1' -sid 'S-1-5-18'
 
    .EXAMPLE
        New-SvcAce -ComputerName 'server1' -ServiceName 'service1' -sid 'S-1-5-18' -accessMask 0x2009D
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Position=0,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq $ENV:COMPUTERNAME}{
                    $true
                }
                {$_ -ne $ENV:COMPUTERNAME} {
                    if (Test-WSMan -ComputerName $_ -ErrorAction SilentlyContinue) {
                        $true
                    }
                    else {
                        $false
                        throw "Can't connect to remote computer $_ .."
                    }
                }
            }
        })]
        [string]$ComputerName,
        [Parameter(
            Position=1,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq 'scmanager'}{
                    $true
                    break
                }
                {$ComputerName -eq $ENV:COMPUTERNAME} {
                    if ($_ -eq (Get-Service -Name $_ -ErrorAction SilentlyContinue).Name) {
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
                {$ComputerName -ne $ENV:COMPUTERNAME} {
                    if ($_ -eq (Invoke-Command -ComputerName $ComputerName -ScriptBlock{Get-Service -Name $using:_ -ErrorAction SilentlyContinue})){
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
            }
        })]
        [string]$ServiceName,
        [Parameter(
            Position=2,
            Mandatory=$false
        )]
        [ValidateScript({
            $SidTokens = @(
            "DA","DG","DU","ED","DD","DC","BA","BG","BU","LA","LG","AO","BO","PO","SO","AU","PS","CO","CG","SY","PU","WD","RE","IU","NU","SU","RC",
            "WR","AN","SA","CA","RS","EA","PA","RU","LS","NS","RD","NO","MU","LU","IS","CY","OW","ER","RO","CD","AC","RA","ES","MS","UD","HA","CN",
            "AA","RM","LW","ME","MP","HI","SI"
            )
            if ($SidTokens -contains $_) {
                $false
                throw "$_ is a sid-token. An abbreviated form of a well-known SID, and should not be modified."
            }
            else {
                $true
            }
        })]
        [ValidateNotNullOrEmpty()]
        [string]$sid = $ENV:SvcAceSID,
        [Parameter(
            Position=3,
            Mandatory=$false
        )]
        [ValidateNotNullOrEmpty()]
        [int]$AccessMask = 0x2009D
    )

    ### Checking if code is running in elevated session
    $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($id)
    if (!($Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))){
        Throw "Script is not running as Administrator. Stopping script, no changes were made .."
    }  

    ### Creating security identifier for sid
    $sid = New-Object System.Security.Principal.SecurityIdentifier($sid)

    ### Setting accessmask for 'scmanager' to a supported value
    if ($ServiceName -eq "scmanager") {
        Write-Information -MessageData "Accessmask for the service control manager has been corrected to 0x2001D (Supported)" -InformationAction Continue
        [int]$AccessMask = 0x2001D
    }

    ### Getting SDDL and checking if sid has an Ace on scm as a friendly reminder
    switch ($ServiceName) {
        {@($ComputerName -ne $ENV:COMPUTERNAME) -and ($_ -ne "scmanager")} {
                $scmSDDL = Invoke-Command -ScriptBlock {sc.exe sdshow scmanager | Where-Object {$_}} -ComputerName $ComputerName -ErrorAction Stop
                $sddl = Invoke-Command -ScriptBlock {sc.exe sdshow $using:ServiceName | Where-Object {$_}} -ComputerName $ComputerName -ErrorAction Stop
        }
        {@($ComputerName -eq $ENV:COMPUTERNAME) -and ($_ -ne "scmanager")} {
                $scmSDDL = sc.exe sdshow scmanager | Where-Object {$_} -ErrorAction Stop
                $sddl = sc.exe sdshow $ServiceName | Where-Object {$_} -ErrorAction Stop
        }
        {@($ComputerName -ne $ENV:COMPUTERNAME) -and ($_ -eq "scmanager")} {
                $sddl = Invoke-Command -ScriptBlock {sc.exe sdshow $using:ServiceName | Where-Object {$_}} -ComputerName $ComputerName -ErrorAction Stop
        }
        {@($ComputerName -eq $ENV:COMPUTERNAME) -and ($_ -eq "scmanager")} {
                $sddl = sc.exe sdshow $ServiceName | Where-Object {$_} -ErrorAction Stop
        }
    }
    if (($null -ne $scmSDDL) -and (($scmSDDL | Out-String) -notlike "*OpenService FAILED*")) {
        $scmRawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($scmSDDL) -ErrorAction SilentlyContinue
        if ($scmRawSD.DiscretionaryAcl.SecurityIdentifier.Value -notcontains $sid) {
            Write-Information -MessageData "Additional info: SID does not have an Ace on the service control manager" -InformationAction Continue
        }
    }

    ### Converting SDDL to RawSD
    $RawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)

    ### Creating Ace with accessmask and sid
    $ace = New-Object System.Security.AccessControl.CommonAce([System.Security.AccessControl.AceFlags]::None,[System.Security.AccessControl.AceQualifier]::AccessAllowed,$AccessMask,$sid,$false,$null)

    ### Checking if raw security descriptor already contains ACE
    if ($RawSD.DiscretionaryAcl.GetEnumerator() -notcontains $ace){
       
        ### Adding Ace to RawSD
        $RawSD.DiscretionaryAcl.InsertAce($RawSD.DiscretionaryAcl.Count,$ace)

        ### Converting RawSD to SDDL string
        $newSDDL = $RawSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)

        ### Setting SDDL
        switch ($ComputerName) {
            {$ComputerName -ne $ENV:COMPUTERNAME} {
                Write-Output ""
                Invoke-Command -ScriptBlock {sc.exe sdset $using:ServiceName $using:newSDDL} -ComputerName $ComputerName -ErrorAction Stop
                Write-Output ""
            }
            {$ComputerName -eq $ENV:COMPUTERNAME} {
                Write-Output ""
                sc.exe sdset $ServiceName $newSDDL -ErrorAction Stop
                Write-Output ""
            }
        }    
    }
    else {
        Write-Host "`r`nThe Access Control Entry already exist on the service ""$ServiceName"", no change was made.`r`n" -ForegroundColor Green
    }
}
function Remove-SvcAce {

    <#
    .SYNOPSIS
        Configures the security descriptor string of a specified service to remove an
        access control entry with the sid and permissions specified.
     
    .DESCRIPTION
        Remove-AccessControlEntry is a function that modifies the security descriptor string
        on a defined service, by removing an Ace for a SID, eg. group or user, that has access.
 
    .PARAMETER ComputerName
        Specifies the name of the remote server to execute this script on.
     
    .PARAMETER ServiceName
        Specifies the shortname of the service that an Ace will be removed from.
 
    .PARAMETER sid
        Specifies the SID of an identity, eg. user or group that the access is granted for.
     
    .PARAMETER accessMask
        Specifies the access mask in HEX that translates into the permissions that is granted in the ACE.
        If no value is supplied, the default accessMask will be "0x2009D" which is HEX for the permissions "CCLCSWRPLORC",
        which is needed to poll services for monitoring data.
        If scmanager is defined as ServiceName, the accessMask will be corrected to "0x2001D" as that is what is supported for scmanager.
        See more information: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f4296d69-1c0f-491f-9587-a960b292d070
 
    .EXAMPLE
        Remove-AccessControlEntry -ComputerName 'server1' -ServiceName 'service1' -sid 'S-1-5-18'
 
    .EXAMPLE
        Remove-AccessControlEntry -ComputerName 'server1' -ServiceName 'service1' -sid 'S-1-5-18' -accessMask 0x2009D
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Position=0,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq $ENV:COMPUTERNAME}{
                    $true
                }
                {$_ -ne $ENV:COMPUTERNAME} {
                    if (Test-WSMan -ComputerName $_ -ErrorAction SilentlyContinue) {
                        $true
                    }
                    else {
                        $false
                        throw "Can't connect to remote computer $_ .."
                    }
                }
            }
        })]
        [string]$ComputerName,
        [Parameter(
            Position=1,
            Mandatory=$true
        )]
        [ValidateScript({
            switch ($_) {
                {$_ -eq 'scmanager'}{
                    $true
                    break
                }
                {$ComputerName -eq $ENV:COMPUTERNAME} {
                    if ($_ -eq (Get-Service -Name $_ -ErrorAction SilentlyContinue).Name) {
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
                {$ComputerName -ne $ENV:COMPUTERNAME} {
                    if ($_ -eq (Invoke-Command -ComputerName $ComputerName -ScriptBlock{Get-Service -Name $using:_ -ErrorAction SilentlyContinue})){
                        $true
                    }
                    else {
                        $false
                        throw "$_ is not a valid service name. Try using the ""Get-Service"" cmdlet to get the correct shortname of the service."
                    }
                }
            }
        })]
        [string]$ServiceName,
        [Parameter(
            Position=2,
            Mandatory=$false
        )]
        [ValidateScript({
            $SidTokens = @(
            "DA","DG","DU","ED","DD","DC","BA","BG","BU","LA","LG","AO","BO","PO","SO","AU","PS","CO","CG","SY","PU","WD","RE","IU","NU","SU","RC",
            "WR","AN","SA","CA","RS","EA","PA","RU","LS","NS","RD","NO","MU","LU","IS","CY","OW","ER","RO","CD","AC","RA","ES","MS","UD","HA","CN",
            "AA","RM","LW","ME","MP","HI","SI"
            )
            if ($SidTokens -contains $_) {
                $false
                throw "$_ is a sid-token. An abbreviated form of a well-known SID, and should not be modified."
            }
            else {
                $true
            }
        })]
        [ValidateNotNullOrEmpty()]
        [string]$sid = $ENV:SvcAceSID,
        [Parameter(
            Position=3,
            Mandatory=$false
        )]
        [ValidateNotNullOrEmpty()]
        [int]$AccessMask = 0x2009D
    )
    
    ### Checking if code is running in elevated session
    $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($id)
    if (!($Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))){
        Throw "Script is not running as Administrator. Stopping script, no changes were made .."
    }  

    ### Creating security identifier for sid
    $sid = New-Object System.Security.Principal.SecurityIdentifier($sid)

    ### Getting SDDL
    switch ($ComputerName) {
        {@($_ -ne $ENV:COMPUTERNAME)} {
                $sddl = Invoke-Command -ScriptBlock {sc.exe sdshow $using:ServiceName | Where-Object {$_}} -ComputerName $_ -ErrorAction Stop
        }
        {@($_ -eq $ENV:COMPUTERNAME)} {
                $sddl = sc.exe sdshow $ServiceName | Where-Object {$_} -ErrorAction Stop
        }
    }

    ### Converting SDDL to RawSD
    $RawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)

    ### Checking if SID has an entry and removing it
    if ($RawSD.DiscretionaryAcl.SecurityIdentifier -contains $sid){
        try {
            $i = 0
            foreach ($a in $RawSD.DiscretionaryAcl){
                if (($a.SecurityIdentifier.Value -eq $sid) -and ($a.AccessMask -eq [uint32]$AccessMask)) {
                    [int]$index = $i
                    $RawSD.DiscretionaryAcl.RemoveAce($index)
                    Write-Output "`r`nAn ACE for the SID ($sid) with the access mask $AccessMask has been removed"
                }
                $i++
            }
        }
        catch {
            Throw "Something went wrong trying to remove existing ACE .. Stopping script, no changes was made .."
        }

        ### Converting RawSD to SDDL string
        $newSDDL = $rawSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All)

        ### Comparing old and new SDDL string for differences"
        if (!(Compare-Object $sddl $newSDDL)) {
            Throw "The SID has an Ace, but the entered accessmask does not match the entry. Stopping script, no change was made .."
        }

        ### Setting SDDL
        switch ($ComputerName) {
            {$ComputerName -ne $ENV:COMPUTERNAME} {
                Write-Output ""
                Invoke-Command -ScriptBlock {sc.exe sdset $using:ServiceName $using:newSDDL} -ComputerName $ComputerName -ErrorAction Stop
                Write-Output ""
            }
            {$ComputerName -eq $ENV:COMPUTERNAME} {
                Write-Output ""
                sc.exe sdset $ServiceName $newSDDL -ErrorAction Stop
                Write-Output ""
            }
        }
    }
    else {
        Write-Host "`r`nNo access control entry exist for those params, no change was made.`r`n" -ForegroundColor Green
    }       
}
Export-ModuleMember -Function Get-SvcSddl, Get-SvcAce, New-SvcAce, Remove-SvcAce