Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# ------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All Rights Reserved.
# Licensed under the MIT License. See License in the project root for license information.
# ------------------------------------------------------------------------------
function New-EntraServicePrincipalKeyCredential {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the key credential will be added.")]
[Alias("ObjectId")]
[ValidateNotNullOrEmpty()]
[System.String]$ServicePrincipalId,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true,
HelpMessage = "Specifies the value for the public key encoded in Base64.")]
[System.String]$Value,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of key credential (e.g., AsymmetricX509Cert, Symmetric).")]
[ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')]
[System.String]$Type,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the usage of the key credential (e.g., Sign, Verify).")]
[ValidateSet('Sign', 'Verify')]
[System.String]$Usage,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "A self-signed JWT token used as a proof of possession of the existing keys. This JWT token must be signed with a private key that corresponds to one of the existing valid certificates associated with the servicePrincipal.")]
[System.String]$Proof,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies a custom identifier for the key credential.")]
[System.String] $CustomKeyIdentifier,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the time when the key becomes valid as a DateTime object.")]
[System.Nullable[System.DateTime]] $StartDate,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true,
HelpMessage = "Specifies the time when the key becomes invalid as a DateTime object.")]
[System.Nullable[System.DateTime]] $EndDate
)

begin {
# Ensure connection to Microsoft Entra
if (-not (Get-EntraContext)) {
$errorMessage = "Not connected to Microsoft Graph. Use 'Connect-Entra -Scopes User.Read.All' to authenticate."
Write-Error -Message $errorMessage -ErrorAction Stop
return
}
}

PROCESS {

$customHeaders = New-EntraCustomHeaders -Command $MyInvocation.MyCommand
$customHeaders['Content-Type'] = 'application/json'
$baseUri = '/v1.0/servicePrincipals'
$URI = "$baseUri/$ServicePrincipalId/addKey"

$params = @{
keyCredential = @{
type = $Type
usage = $Usage
key = $Value
startDateTime = $StartDate
endDateTime = $EndDate
customKeyIdentifier = $CustomKeyIdentifier
}
passwordCredential = $null
proof = $Proof
}

Write-Debug("============================ TRANSFORMATIONS ============================")
$params.Keys | ForEach-Object { "$_ : $($params[$_])" } | Write-Debug
Write-Debug("=========================================================================`n")
$tesy = ($params | ConvertTo-Json -Depth 4)

try {
$response = Invoke-GraphRequest -Headers $customHeaders -Uri $URI -Method "POST" -Body ($params | ConvertTo-Json -Depth 4)

$targetTypeList = @()
foreach ($data in $response) {
$target = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential
$data.PSObject.Properties | ForEach-Object {
$propertyName = $_.Name.Substring(0, 1).ToUpper() + $_.Name.Substring(1)
$propertyValue = $_.Value
$target | Add-Member -MemberType NoteProperty -Name $propertyName -Value $propertyValue -Force
}
$targetTypeList += $target
}
$targetTypeList
}
catch {
Write-Error "Failed to add key credential: $($_)"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# ------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All Rights Reserved.
# Licensed under the MIT License. See License in the project root for license information.
# ------------------------------------------------------------------------------
function New-EntraBetaServicePrincipalKeyCredential {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the key credential will be added.")]
[Alias("ObjectId")]
[ValidateNotNullOrEmpty()]
[System.String]$ServicePrincipalId,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true,
HelpMessage = "Specifies the value for the public key encoded in Base64.")]
[System.String]$Value,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of key credential (e.g., AsymmetricX509Cert, Symmetric).")]
[ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')]
[System.String]$Type,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the usage of the key credential (e.g., Sign, Verify).")]
[ValidateSet('Sign', 'Verify')]
[System.String]$Usage,

[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "A self-signed JWT token used as a proof of possession of the existing keys. This JWT token must be signed with a private key that corresponds to one of the existing valid certificates associated with the servicePrincipal.")]
[System.String]$Proof,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies a custom identifier for the key credential.")]
[System.String] $CustomKeyIdentifier,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the time when the key becomes valid as a DateTime object.")]
[System.Nullable[System.DateTime]] $StartDate,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true,
HelpMessage = "Specifies the time when the key becomes invalid as a DateTime object.")]
[System.Nullable[System.DateTime]] $EndDate
)

begin {
# Ensure connection to Microsoft Entra
if (-not (Get-EntraContext)) {
$errorMessage = "Not connected to Microsoft Graph. Use 'Connect-Entra -Scopes User.Read.All' to authenticate."
Write-Error -Message $errorMessage -ErrorAction Stop
return
}
}

PROCESS {

$customHeaders = New-EntraBetaCustomHeaders -Command $MyInvocation.MyCommand
$customHeaders['Content-Type'] = 'application/json'
$baseUri = '/beta/servicePrincipals'
$URI = "$baseUri/$ServicePrincipalId/addKey"

$params = @{
keyCredential = @{
type = $Type
usage = $Usage
key = $Value
startDateTime = $StartDate
endDateTime = $EndDate
customKeyIdentifier = $CustomKeyIdentifier
}
passwordCredential = $null
proof = $Proof
}

Write-Debug("============================ TRANSFORMATIONS ============================")
$params.Keys | ForEach-Object { "$_ : $($params[$_])" } | Write-Debug
Write-Debug("=========================================================================`n")

try {
$response = Invoke-GraphRequest -Headers $customHeaders -Uri $URI -Method "POST" -Body ($params | ConvertTo-Json -Depth 4)
$targetTypeList = @()
foreach ($data in $response) {
$target = New-Object Microsoft.Graph.Beta.PowerShell.Models.MicrosoftGraphKeyCredential
$data.PSObject.Properties | ForEach-Object {
$propertyName = $_.Name.Substring(0, 1).ToUpper() + $_.Name.Substring(1)
$propertyValue = $_.Value
$target | Add-Member -MemberType NoteProperty -Name $propertyName -Value $propertyValue -Force
}
$targetTypeList += $target
}
$targetTypeList
}
catch {
Write-Error "Failed to add key credential: $($_)"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# ------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
# ------------------------------------------------------------------------------
BeforeAll {
if ((Get-Module -Name Microsoft.Entra.Applications) -eq $null) {
Import-Module Microsoft.Entra.Applications
}
Import-Module (Join-Path $PSScriptRoot "..\..\Common-Functions.ps1") -Force

$response = @{
"@odata.context" = 'https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.keyCredential'
"customKeyIdentifier" = $null
"endDateTime" = "01/15/2027 14:22:00"
"key" = $null
"keyId" = "aaaaaaaa-0b0b-1c1c-2d2d-333333"
"startDateTime" = "01/15/2025 14:22:00"
"type" = "AsymmetricX509Cert"
"usage" = "Verify"
}

Mock -CommandName Invoke-MgGraphRequest -MockWith { $response } -ModuleName Microsoft.Entra.Applications
Mock -CommandName Get-EntraContext -MockWith { @{Scopes = @("Application.ReadWrite.All") } } -ModuleName Microsoft.Entra.Applications
}

Describe "New-EntraServicePrincipalKeyCredential" {
Context "Test for New-EntraServicePrincipalKeyCredential" {

It "Should fail when ServicePrincipalId is null" {
{ New-EntraServicePrincipalKeyCredential -ServicePrincipalId -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*"
}

It "Should fail when ServicePrincipalId is empty" {
{ New-EntraServicePrincipalKeyCredential -ServicePrincipalId ""} | Should -Throw "Cannot validate argument on parameter 'ServicePrincipalId'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again."
}

It "Should fail when proof is empty" {
{ New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof ""} | Should -Throw "Cannot bind argument to parameter 'Proof' because it is an empty string."
}

It "Should return created service principal key credential when ServicePrincipalId is valid and all required parameters are provided" {
$result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m"
$result | Should -Not -BeNullOrEmpty

Should -Invoke -CommandName Invoke-MgGraphRequest -ModuleName Microsoft.Entra.Applications -Times 1
}

It "Should contain 'User-Agent' header" {
$userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraServicePrincipalKeyCredential"
$result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m"
$result | Should -Not -BeNullOrEmpty
$userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraServicePrincipalKeyCredential"
Should -Invoke -CommandName Invoke-GraphRequest -ModuleName Microsoft.Entra.Applications -Times 1 -ParameterFilter {
$Headers.'User-Agent' | Should -Be $userAgentHeaderValue
$true
}
}

It "Should execute successfully without throwing an error" {
# Disable confirmation prompts
$originalDebugPreference = $DebugPreference
$DebugPreference = 'Continue'
try {
# Act & Assert: Ensure the function doesn't throw an exception
{ New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m" -Debug } | Should -Not -Throw
}
finally {
# Restore original confirmation preference
$DebugPreference = $originalDebugPreference
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# ------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
# ------------------------------------------------------------------------------
BeforeAll {
if ((Get-Module -Name Microsoft.Entra.Beta.Applications) -eq $null) {
Import-Module Microsoft.Entra.Beta.Applications
}
Import-Module (Join-Path $PSScriptRoot "..\..\Common-Functions.ps1") -Force

$response = @{
"@odata.context" = 'https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.keyCredential'
"customKeyIdentifier" = $null
"endDateTime" = "01/15/2027 14:22:00"
"key" = $null
"keyId" = "aaaaaaaa-0b0b-1c1c-2d2d-333333"
"startDateTime" = "01/15/2025 14:22:00"
"type" = "AsymmetricX509Cert"
"usage" = "Verify"
}

Mock -CommandName Invoke-MgGraphRequest -MockWith { $response } -ModuleName Microsoft.Entra.Beta.Applications
Mock -CommandName Get-EntraContext -MockWith { @{Scopes = @("Application.ReadWrite.All") } } -ModuleName Microsoft.Entra.Beta.Applications
}

Describe "New-EntraBetaServicePrincipalKeyCredential" {
Context "Test for New-EntraBetaServicePrincipalKeyCredential" {

It "Should fail when ServicePrincipalId is null" {
{ New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*"
}

It "Should fail when ServicePrincipalId is empty" {
{ New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId ""} | Should -Throw "Cannot validate argument on parameter 'ServicePrincipalId'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again."
}

It "Should fail when proof is empty" {
{ New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof ""} | Should -Throw "Cannot bind argument to parameter 'Proof' because it is an empty string."
}

It "Should return created service principal key credential when ServicePrincipalId is valid and all required parameters are provided" {
$result = New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m"
$result | Should -Not -BeNullOrEmpty

Should -Invoke -CommandName Invoke-MgGraphRequest -ModuleName Microsoft.Entra.Beta.Applications -Times 1
}

It "Should contain 'User-Agent' header" {
$userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraBetaServicePrincipalKeyCredential"
$result = New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m"
$result | Should -Not -BeNullOrEmpty
$userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraBetaServicePrincipalKeyCredential"
Should -Invoke -CommandName Invoke-GraphRequest -ModuleName Microsoft.Entra.Beta.Applications -Times 1 -ParameterFilter {
$Headers.'User-Agent' | Should -Be $userAgentHeaderValue
$true
}
}

It "Should execute successfully without throwing an error" {
# Disable confirmation prompts
$originalDebugPreference = $DebugPreference
$DebugPreference = 'Continue'
try {
# Act & Assert: Ensure the function doesn't throw an exception
{ New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "AsymmetricX509Cert" -Usage "Verify" -Proof "c29tZVByb29m" -Debug } | Should -Not -Throw
}
finally {
# Restore original confirmation preference
$DebugPreference = $originalDebugPreference
}
}
}

}