Public/Persistence/Set-ServicePrincipalCredential.ps1
function Set-ServicePrincipalCredential { [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'AddPassword')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidatePattern('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$', ErrorMessage = "It does not match expected GUID pattern")] [Alias('Id', 'object-id', 'application-id')] [string]$ObjectId, [Parameter(Mandatory = $true, ParameterSetName = 'AddPassword')] [Parameter(Mandatory = $true, ParameterSetName = 'AddCertificate')] [Parameter(Mandatory = $true, ParameterSetName = 'RemoveCredential')] [ValidateSet('AddPassword', 'AddCertificate', 'RemovePassword', 'RemoveCertificate')] [string]$Action, [Parameter(Mandatory = $false, ParameterSetName = 'AddPassword')] [string]$DisplayName = "BlackCat-Generated-Secret", [Parameter(Mandatory = $false, ParameterSetName = 'AddPassword')] [datetime]$EndDateTime = (Get-Date).AddYears(2), [Parameter(Mandatory = $true, ParameterSetName = 'AddCertificate')] [string]$CertificateData, [Parameter(Mandatory = $false, ParameterSetName = 'AddCertificate')] [string]$CertificateDisplayName = "BlackCat-Generated-Certificate", [Parameter(Mandatory = $false, ParameterSetName = 'AddCertificate')] [datetime]$CertificateEndDateTime = (Get-Date).AddYears(2), [Parameter(Mandatory = $true, ParameterSetName = 'RemoveCredential')] [ValidatePattern('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$', ErrorMessage = "It does not match expected GUID pattern")] [string]$KeyId, [Parameter(Mandatory = $false, ParameterSetName = 'AddPassword')] [switch]$GenerateSecret, [Parameter(Mandatory = $false)] [switch]$UseApplicationEndpoint ) begin { Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)" $MyInvocation.MyCommand.Name | Invoke-BlackCat -ResourceTypeName 'MSGraph' } process { try { # Determine if we're working with an Application or Service Principal $entityType = if ($UseApplicationEndpoint) { "applications" } else { "servicePrincipals" } # First, verify the object exists Write-Verbose "Verifying $entityType with ObjectId: $ObjectId" $entity = Invoke-MsGraph -relativeUrl "$entityType/$ObjectId" -NoBatch -ErrorAction SilentlyContinue if (-not $entity) { Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "$entityType with ObjectId '$ObjectId' not found." -Severity 'Error' return } Write-Verbose "Found ${entityType}: $($entity.displayName)" switch ($Action) { 'AddPassword' { if ($PSCmdlet.ShouldProcess("$entityType '$($entity.displayName)'", "Add password credential")) { $uri = "$($sessionVariables.graphUri)/$entityType/$ObjectId/addPassword" $passwordCredential = @{ displayName = $DisplayName endDateTime = $EndDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") } if ($GenerateSecret) { # Let Microsoft Graph generate the secret $body = @{ passwordCredential = $passwordCredential } | ConvertTo-Json -Depth 3 } else { # Generate our own secret $secretValue = [System.Web.Security.Membership]::GeneratePassword(32, 8) $passwordCredential.secretText = $secretValue $body = @{ passwordCredential = $passwordCredential } | ConvertTo-Json -Depth 3 } $requestParam = @{ Headers = $script:graphHeader Uri = $uri Method = 'POST' Body = $body ContentType = 'application/json' UserAgent = $sessionVariables.userAgent } Write-Verbose "Adding password credential to $entityType" $response = Invoke-RestMethod @requestParam Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Password credential added successfully with KeyId: $($response.keyId)" -Severity 'Information' return $response } } 'AddCertificate' { if ($PSCmdlet.ShouldProcess("$entityType '$($entity.displayName)'", "Add certificate credential")) { $uri = "$($sessionVariables.graphUri)/$entityType/$ObjectId/addKey" # Validate certificate data format (should be base64 encoded) try { [System.Convert]::FromBase64String($CertificateData) | Out-Null } catch { Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Certificate data must be base64 encoded" -Severity 'Error' return } $keyCredential = @{ type = "AsymmetricX509Cert" usage = "Verify" key = $CertificateData displayName = $CertificateDisplayName endDateTime = $CertificateEndDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") } $body = @{ keyCredential = $keyCredential passwordCredential = $null proof = $null } | ConvertTo-Json -Depth 3 $requestParam = @{ Headers = $script:graphHeader Uri = $uri Method = 'POST' Body = $body ContentType = 'application/json' UserAgent = $sessionVariables.userAgent } Write-Verbose "Adding certificate credential to $entityType" $response = Invoke-RestMethod @requestParam Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Certificate credential added successfully with KeyId: $($response.keyId)" -Severity 'Information' return $response } } 'RemovePassword' { if ($PSCmdlet.ShouldProcess("$entityType '$($entity.displayName)'", "Remove password credential '$KeyId'")) { $uri = "$($sessionVariables.graphUri)/$entityType/$ObjectId/removePassword" $body = @{ keyId = $KeyId } | ConvertTo-Json $requestParam = @{ Headers = $script:graphHeader Uri = $uri Method = 'POST' Body = $body ContentType = 'application/json' UserAgent = $sessionVariables.userAgent } Write-Verbose "Removing password credential from $entityType" Invoke-RestMethod @requestParam Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Password credential with KeyId '$KeyId' removed successfully" -Severity 'Information' } } 'RemoveCertificate' { if ($PSCmdlet.ShouldProcess("$entityType '$($entity.displayName)'", "Remove certificate credential '$KeyId'")) { $uri = "$($sessionVariables.graphUri)/$entityType/$ObjectId/removeKey" $body = @{ keyId = $KeyId proof = $null } | ConvertTo-Json $requestParam = @{ Headers = $script:graphHeader Uri = $uri Method = 'POST' Body = $body ContentType = 'application/json' UserAgent = $sessionVariables.userAgent } Write-Verbose "Removing certificate credential from $entityType" Invoke-RestMethod @requestParam Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Certificate credential with KeyId '$KeyId' removed successfully" -Severity 'Information' } } } } catch { Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error' } } end { Write-Verbose "Completed function $($MyInvocation.MyCommand.Name)" } <# .SYNOPSIS Manages credentials (passwords and certificates) for Microsoft Entra applications and service principals. .DESCRIPTION The Set-ServicePrincipalCredential function allows you to add, remove, and manage credentials for Microsoft Entra applications and service principals. It supports both password credentials and certificate credentials, and can work with either the applications or servicePrincipals endpoints. .PARAMETER ObjectId The Object ID (GUID) of the Microsoft Entra application or service principal. This parameter is mandatory and must match the pattern of a valid GUID. .PARAMETER Action Specifies the action to perform on the credential. Valid values are: - AddPassword: Adds a new password credential - AddCertificate: Adds a new certificate credential - RemovePassword: Removes an existing password credential - RemoveCertificate: Removes an existing certificate credential .PARAMETER DisplayName The display name for the password credential. Only used with AddPassword action. Defaults to "BlackCat-Generated-Secret". .PARAMETER EndDateTime The expiration date and time for the password credential. Only used with AddPassword action. Defaults to 2 years from now. .PARAMETER CertificateData Base64-encoded certificate data for the certificate credential. Required for AddCertificate action. .PARAMETER CertificateDisplayName The display name for the certificate credential. Only used with AddCertificate action. Defaults to "BlackCat-Generated-Certificate". .PARAMETER CertificateEndDateTime The expiration date and time for the certificate credential. Only used with AddCertificate action. Defaults to 2 years from now. .PARAMETER KeyId The unique identifier (GUID) of the credential to remove. Required for RemovePassword and RemoveCertificate actions. .PARAMETER GenerateSecret When specified with AddPassword, lets Microsoft Graph generate the secret value. Otherwise, a random secret is generated locally. .PARAMETER UseApplicationEndpoint When specified, uses the applications endpoint instead of servicePrincipals endpoint. Use this when working with application registrations directly. .EXAMPLE Set-ServicePrincipalCredential -ObjectId "12345678-1234-1234-1234-123456789012" -Action AddPassword -DisplayName "MyApp-Secret" Adds a new password credential to the specified service principal with a custom display name. .EXAMPLE Set-ServicePrincipalCredential -ObjectId "12345678-1234-1234-1234-123456789012" -Action AddPassword -GenerateSecret Adds a new password credential where Microsoft Graph generates the secret value. .EXAMPLE Set-ServicePrincipalCredential -ObjectId "12345678-1234-1234-1234-123456789012" -Action AddCertificate -CertificateData "MIIC..." -CertificateDisplayName "MyApp-Cert" Adds a new certificate credential to the specified service principal. .EXAMPLE Set-ServicePrincipalCredential -ObjectId "12345678-1234-1234-1234-123456789012" -Action RemovePassword -KeyId "87654321-4321-4321-4321-210987654321" Removes the password credential with the specified KeyId from the service principal. .EXAMPLE Set-ServicePrincipalCredential -ObjectId "12345678-1234-1234-1234-123456789012" -Action AddPassword -UseApplicationEndpoint Adds a password credential using the applications endpoint instead of servicePrincipals endpoint. .NOTES - This function requires authentication to the Microsoft Graph API with appropriate permissions - For password credentials: Application.ReadWrite.All or Directory.ReadWrite.All - For certificate credentials: Application.ReadWrite.All or Directory.ReadWrite.All - The function supports both applications and servicePrincipals endpoints - Certificate data must be provided as base64-encoded string - Generated secrets are returned in the response for AddPassword actions .LINK https://learn.microsoft.com/en-us/graph/api/application-addpassword https://learn.microsoft.com/en-us/graph/api/application-addkey https://learn.microsoft.com/en-us/graph/api/application-removepassword https://learn.microsoft.com/en-us/graph/api/application-removekey #> } |