From c906d57a73c78ee62bbf67e48853691184d0d868 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 11 Jun 2025 11:32:43 +0300 Subject: [PATCH 01/21] add New-EntraServicePrincipalKeyCredential --- ...New-EntraServicePrincipalKeyCredential.ps1 | 100 ++++++++++++++++++ ...EntraBetaServicePrincipalKeyCredential.ps1 | 100 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 create mode 100644 module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 new file mode 100644 index 0000000000..b4438a01a8 --- /dev/null +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -0,0 +1,100 @@ +function New-EntraServicePrincipalKeyCredential { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [Alias("ObjectId")] + [ValidateNotNullOrEmpty()] + [System.String]$ServicePrincipalId, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$Value, + + [Parameter(Mandatory = $true)] + [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] + [System.String]$Type, + + [Parameter(Mandatory = $true)] + [ValidateSet('Sign', 'Verify')] + [System.String]$Usage, + + [Parameter(Mandatory = $true)] + [System.String]$Proof, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$CustomKeyIdentifier, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.Nullable[System.DateTime]] $StartDate, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + [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 + } + passwordCredential = $null + proof = $Proof + } + + if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + $params["passwordCredential"] = @{ + secretText = $PSBoundParameters["CustomKeyIdentifier"] + } + } + + 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) + + $response | ForEach-Object { + if ($null -ne $_) { + Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime + Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime + } + } + + $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: $($_.Exception.Message)" + } + } +} + diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 new file mode 100644 index 0000000000..078d0946e0 --- /dev/null +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -0,0 +1,100 @@ +function New-EntraBetaServicePrincipalKeyCredential { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [Alias("ObjectId")] + [ValidateNotNullOrEmpty()] + [System.String]$ServicePrincipalId, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$Value, + + [Parameter(Mandatory = $true)] + [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] + [System.String]$Type, + + [Parameter(Mandatory = $true)] + [ValidateSet('Sign', 'Verify')] + [System.String]$Usage, + + [Parameter(Mandatory = $true)] + [System.String]$Proof, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$CustomKeyIdentifier, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.Nullable[System.DateTime]] $StartDate, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + [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 + } + passwordCredential = $null + proof = $Proof + } + + if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + $params["passwordCredential"] = @{ + secretText = $PSBoundParameters["CustomKeyIdentifier"] + } + } + + 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) + + $response | ForEach-Object { + if ($null -ne $_) { + Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime + Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime + } + } + + $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: $($_.Exception.Message)" + } + } +} + From 37be4b6f9d318e7a30eb12459ff0ee3abec1743b Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 08:31:03 +0300 Subject: [PATCH 02/21] refactor and add tests for New-EntraServicePrincipalKeyCredentials --- ...New-EntraServicePrincipalKeyCredential.ps1 | 22 +++--- ...EntraBetaServicePrincipalKeyCredential.ps1 | 25 +++--- ...traServicePrincipalKeyCredential.Tests.ps1 | 78 +++++++++++++++++++ 3 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index b4438a01a8..8c28568d96 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -1,35 +1,35 @@ function New-EntraServicePrincipalKeyCredential { [CmdletBinding()] param ( - [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [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 start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + HelpMessage = "Specifies the value for the key encoded in Base64.")] [System.String]$Value, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] [System.String]$Type, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the key usage.")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, - [Parameter(Mandatory = $true)] + [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 the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] - [System.String]$CustomKeyIdentifier, + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] + [System.String]$PasswordCredential, [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + 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 end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + HelpMessage = "Specifies the time when the key becomes invalid as a DateTime object.")] [System.Nullable[System.DateTime]] $EndDate ) @@ -61,6 +61,10 @@ function New-EntraServicePrincipalKeyCredential { } if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + Write-Error -Message $errorMessage -ErrorAction Stop + } $params["passwordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 078d0946e0..40f442c5c2 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -1,35 +1,35 @@ function New-EntraBetaServicePrincipalKeyCredential { [CmdletBinding()] param ( - [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [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 start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + HelpMessage = "Specifies the value for the key encoded in Base64.")] [System.String]$Value, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] [System.String]$Type, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the key usage.")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, - [Parameter(Mandatory = $true)] + [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 the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] - [System.String]$CustomKeyIdentifier, + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] + [System.String]$PasswordCredential, [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + 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 end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + HelpMessage = "Specifies the time when the key becomes invalid as a DateTime object.")] [System.Nullable[System.DateTime]] $EndDate ) @@ -61,6 +61,10 @@ function New-EntraBetaServicePrincipalKeyCredential { } if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + Write-Error -Message $errorMessage -ErrorAction Stop + } $params["passwordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } @@ -96,5 +100,4 @@ function New-EntraBetaServicePrincipalKeyCredential { Write-Error "Failed to add key credential: $($_.Exception.Message)" } } -} - +} \ No newline at end of file diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 new file mode 100644 index 0000000000..ce33a27b86 --- /dev/null +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -0,0 +1,78 @@ +# ------------------------------------------------------------------------------ +# 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 + "displayName" = "Password friendly name" + "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 -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" + } + It "Should fail when ServicePrincipalId is empty" { + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "" -DisplayName "Helpdesk Secret" } | 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 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 fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "Missing an argument for parameter 'PasswordCredential'*" + } + + It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { + $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" -PasswordCredential "MySecretPassword" + $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-EntraServicePrincipalPasswordCredential" + $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" + $result | Should -Not -BeNullOrEmpty + $userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraServicePrincipalPasswordCredential" + 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-EntraServicePrincipalPasswordCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" -Debug } | Should -Not -Throw + } + finally { + # Restore original confirmation preference + $DebugPreference = $originalDebugPreference + } + } + } +} \ No newline at end of file From f8e26c23757ecea8746ceb92fc4561e90d696741 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 09:14:57 +0300 Subject: [PATCH 03/21] refactors --- .../Applications/New-EntraServicePrincipalKeyCredential.ps1 | 4 ++-- .../New-EntraBetaServicePrincipalKeyCredential.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 8c28568d96..28b50cce8b 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -60,8 +60,8 @@ function New-EntraServicePrincipalKeyCredential { proof = $Proof } - if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { - if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ($Type -eq 'X509CertAndPassword') { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 40f442c5c2..377697cab4 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -60,8 +60,8 @@ function New-EntraBetaServicePrincipalKeyCredential { proof = $Proof } - if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { - if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ($Type -eq 'X509CertAndPassword') { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } From cca803074d1a8d2260a0c44a11479a35da792b5a Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 11:53:57 +0300 Subject: [PATCH 04/21] refactors add tests --- ...New-EntraServicePrincipalKeyCredential.ps1 | 19 ++++++------------- ...EntraBetaServicePrincipalKeyCredential.ps1 | 18 +++++------------- ...traServicePrincipalKeyCredential.Tests.ps1 | 17 +++++++++-------- ...rvicePrincipalPasswordCredential.Tests.ps1 | 3 +++ 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 28b50cce8b..0e8be23fbf 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -7,7 +7,7 @@ function New-EntraServicePrincipalKeyCredential { [System.String]$ServicePrincipalId, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the value for the key encoded in Base64.")] + HelpMessage = "Specifies the value for the private key encoded in Base64.")] [System.String]$Value, [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] @@ -24,8 +24,7 @@ function New-EntraServicePrincipalKeyCredential { [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] [System.String]$PasswordCredential, - [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the time when the key becomes valid as a DateTime object.")] + [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, @@ -42,7 +41,6 @@ function New-EntraServicePrincipalKeyCredential { } } - PROCESS { $customHeaders = New-EntraCustomHeaders -Command $MyInvocation.MyCommand @@ -55,17 +53,19 @@ function New-EntraServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value + DateTimeStart = $StartDate + DateTimeEnd = $EndDate } passwordCredential = $null proof = $Proof } if ($Type -eq 'X509CertAndPassword') { - if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["PasswordCredential"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } - $params["passwordCredential"] = @{ + $params["PasswordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } } @@ -77,13 +77,6 @@ function New-EntraServicePrincipalKeyCredential { try { $response = Invoke-GraphRequest -Headers $customHeaders -Uri $URI -Method "POST" -Body ($params | ConvertTo-Json -Depth 4) - $response | ForEach-Object { - if ($null -ne $_) { - Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime - Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime - } - } - $targetTypeList = @() foreach ($data in $response) { $target = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 377697cab4..ecdc25faea 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -6,8 +6,7 @@ function New-EntraBetaServicePrincipalKeyCredential { [ValidateNotNullOrEmpty()] [System.String]$ServicePrincipalId, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the value for the key encoded in Base64.")] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the value for the key encoded in Base64.")] [System.String]$Value, [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] @@ -42,7 +41,6 @@ function New-EntraBetaServicePrincipalKeyCredential { } } - PROCESS { $customHeaders = New-EntraBetaCustomHeaders -Command $MyInvocation.MyCommand @@ -55,17 +53,19 @@ function New-EntraBetaServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value + DateTimeStart = $StartDate + DateTimeEnd = $EndDate } passwordCredential = $null proof = $Proof } if ($Type -eq 'X509CertAndPassword') { - if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["PasswordCredential"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } - $params["passwordCredential"] = @{ + $params["PasswordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } } @@ -76,14 +76,6 @@ function New-EntraBetaServicePrincipalKeyCredential { try { $response = Invoke-GraphRequest -Headers $customHeaders -Uri $URI -Method "POST" -Body ($params | ConvertTo-Json -Depth 4) - - $response | ForEach-Object { - if ($null -ne $_) { - Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime - Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime - } - } - $targetTypeList = @() foreach ($data in $response) { $target = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 index ce33a27b86..19922b2313 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -10,7 +10,6 @@ BeforeAll { $response = @{ "@odata.context" = 'https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.keyCredential' "customKeyIdentifier" = $null - "displayName" = "Password friendly name" "endDateTime" = "01/15/2027 14:22:00" "key" = $null "keyId" = "aaaaaaaa-0b0b-1c1c-2d2d-333333" @@ -27,10 +26,11 @@ Describe "New-EntraServicePrincipalKeyCredential" { Context "Test for New-EntraServicePrincipalKeyCredential" { It "Should fail when ServicePrincipalId is null" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" + { 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 "" -DisplayName "Helpdesk Secret" } | 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." + { 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 return created service principal key credential when ServicePrincipalId is valid and all required parameters are provided" { @@ -41,7 +41,7 @@ Describe "New-EntraServicePrincipalKeyCredential" { } It "Should fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "Missing an argument for parameter 'PasswordCredential'*" + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" } It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { @@ -52,22 +52,23 @@ Describe "New-EntraServicePrincipalKeyCredential" { } It "Should contain 'User-Agent' header" { - $userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraServicePrincipalPasswordCredential" - $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" + $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-EntraServicePrincipalPasswordCredential" + $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-EntraServicePrincipalPasswordCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" -Debug } | Should -Not -Throw + { 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 diff --git a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 index 219d76bd91..58e21061bf 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 @@ -30,9 +30,11 @@ Describe "New-EntraServicePrincipalPasswordCredential" { Should -Invoke -CommandName Invoke-MgGraphRequest -ModuleName Microsoft.Entra.Applications -Times 1 } + It "Should fail when ServicePrincipalId is empty" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" } + It "Should fail when ServicePrincipalId is invalid" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId "" -DisplayName "Helpdesk Secret" } | 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." } @@ -47,6 +49,7 @@ Describe "New-EntraServicePrincipalPasswordCredential" { $true } } + It "Should execute successfully without throwing an error" { # Disable confirmation prompts $originalDebugPreference = $DebugPreference From 47367dc2733ff401c7b0fe96e9e2833a09fb41fb Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 16:04:51 +0300 Subject: [PATCH 05/21] fix failing tests --- ...traServicePrincipalKeyCredential.Tests.ps1 | 5 +- ...taServicePrincipalKeyCredentials.Tests.ps1 | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 index 19922b2313..a510388dfd 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -28,7 +28,7 @@ Describe "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." } @@ -41,7 +41,7 @@ Describe "New-EntraServicePrincipalKeyCredential" { } It "Should fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" } It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { @@ -76,4 +76,5 @@ Describe "New-EntraServicePrincipalKeyCredential" { } } } + } \ No newline at end of file diff --git a/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 b/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 new file mode 100644 index 0000000000..8c794d54c6 --- /dev/null +++ b/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 @@ -0,0 +1,80 @@ +# ------------------------------------------------------------------------------ +# 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 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 fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { + { New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "test" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + } + + It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { + $result = New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" -PasswordCredential "MySecretPassword" + $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 + } + } + } + +} \ No newline at end of file From d57bc747cacb9ef3b8186db1faf6637f6acbc20f Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Fri, 13 Jun 2025 08:44:13 +0300 Subject: [PATCH 06/21] fix response type entra beta key credential --- .../Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index ecdc25faea..ea470788cf 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -78,7 +78,7 @@ function New-EntraBetaServicePrincipalKeyCredential { $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 + $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 From eb436cbb87110f09c1c60b4956bdafe5269ed2a2 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Mon, 16 Jun 2025 10:56:51 +0300 Subject: [PATCH 07/21] restore add Password credential tests --- .../New-EntraServicePrincipalPasswordCredential.Tests.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 index 58e21061bf..219d76bd91 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 @@ -30,11 +30,9 @@ Describe "New-EntraServicePrincipalPasswordCredential" { Should -Invoke -CommandName Invoke-MgGraphRequest -ModuleName Microsoft.Entra.Applications -Times 1 } - It "Should fail when ServicePrincipalId is empty" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" } - It "Should fail when ServicePrincipalId is invalid" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId "" -DisplayName "Helpdesk Secret" } | 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." } @@ -49,7 +47,6 @@ Describe "New-EntraServicePrincipalPasswordCredential" { $true } } - It "Should execute successfully without throwing an error" { # Disable confirmation prompts $originalDebugPreference = $DebugPreference From 722d14c4b04cb815206dc9ed7100cefb1ef19768 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 18 Jun 2025 08:38:57 +0300 Subject: [PATCH 08/21] refactors --- .../New-EntraServicePrincipalKeyCredential.ps1 | 12 ++++++++---- ...-EntraBetaServicePrincipalKeyCredential.ps1 | 18 +++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 0e8be23fbf..5a5dbefea0 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -7,14 +7,14 @@ function New-EntraServicePrincipalKeyCredential { [System.String]$ServicePrincipalId, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the value for the private key encoded in Base64.")] + HelpMessage = "Specifies the value for the public key encoded in Base64.")] [System.String]$Value, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] + [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 key usage.")] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the usage of the key credential (e.g., Sign, Verify).")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, @@ -23,6 +23,9 @@ function New-EntraServicePrincipalKeyCredential { [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] [System.String]$PasswordCredential, + + [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, @@ -55,6 +58,7 @@ function New-EntraServicePrincipalKeyCredential { key = $Value DateTimeStart = $StartDate DateTimeEnd = $EndDate + customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null proof = $Proof @@ -66,7 +70,7 @@ function New-EntraServicePrincipalKeyCredential { Write-Error -Message $errorMessage -ErrorAction Stop } $params["PasswordCredential"] = @{ - secretText = $PSBoundParameters["CustomKeyIdentifier"] + secretText = $PSBoundParameters["PasswordCredential"] } } diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index ea470788cf..8520187405 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -1,19 +1,20 @@ 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.")] + [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 key encoded in Base64.")] + [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 the key.")] + [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 key usage.")] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the usage of the key credential (e.g., Sign, Verify).")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, @@ -22,9 +23,11 @@ function New-EntraBetaServicePrincipalKeyCredential { [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] [System.String]$PasswordCredential, + + [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.")] + [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, @@ -55,6 +58,7 @@ function New-EntraBetaServicePrincipalKeyCredential { key = $Value DateTimeStart = $StartDate DateTimeEnd = $EndDate + customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null proof = $Proof @@ -66,7 +70,7 @@ function New-EntraBetaServicePrincipalKeyCredential { Write-Error -Message $errorMessage -ErrorAction Stop } $params["PasswordCredential"] = @{ - secretText = $PSBoundParameters["CustomKeyIdentifier"] + secretText = $PSBoundParameters["PasswordCredential"] } } From ecb7f5bcbb61b3597f86a1701d5614225130e89e Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 18 Jun 2025 08:41:43 +0300 Subject: [PATCH 09/21] refactors --- .../Applications/New-EntraServicePrincipalKeyCredential.ps1 | 4 ++-- .../New-EntraBetaServicePrincipalKeyCredential.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 5a5dbefea0..f7f0faa8fc 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -56,8 +56,8 @@ function New-EntraServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value - DateTimeStart = $StartDate - DateTimeEnd = $EndDate + dateTimeStart = $StartDate + dateTimeEnd = $EndDate customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 8520187405..ba5bc3b7c8 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -56,8 +56,8 @@ function New-EntraBetaServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value - DateTimeStart = $StartDate - DateTimeEnd = $EndDate + dateTimeStart = $StartDate + dateTimeEnd = $EndDate customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null From 205e7028f77108503bc5f2c2c02e13d4764e4652 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 11 Jun 2025 11:32:43 +0300 Subject: [PATCH 10/21] add New-EntraServicePrincipalKeyCredential --- ...New-EntraServicePrincipalKeyCredential.ps1 | 100 ++++++++++++++++++ ...EntraBetaServicePrincipalKeyCredential.ps1 | 100 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 create mode 100644 module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 new file mode 100644 index 0000000000..b4438a01a8 --- /dev/null +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -0,0 +1,100 @@ +function New-EntraServicePrincipalKeyCredential { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [Alias("ObjectId")] + [ValidateNotNullOrEmpty()] + [System.String]$ServicePrincipalId, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$Value, + + [Parameter(Mandatory = $true)] + [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] + [System.String]$Type, + + [Parameter(Mandatory = $true)] + [ValidateSet('Sign', 'Verify')] + [System.String]$Usage, + + [Parameter(Mandatory = $true)] + [System.String]$Proof, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$CustomKeyIdentifier, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.Nullable[System.DateTime]] $StartDate, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + [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 + } + passwordCredential = $null + proof = $Proof + } + + if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + $params["passwordCredential"] = @{ + secretText = $PSBoundParameters["CustomKeyIdentifier"] + } + } + + 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) + + $response | ForEach-Object { + if ($null -ne $_) { + Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime + Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime + } + } + + $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: $($_.Exception.Message)" + } + } +} + diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 new file mode 100644 index 0000000000..078d0946e0 --- /dev/null +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -0,0 +1,100 @@ +function New-EntraBetaServicePrincipalKeyCredential { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [Alias("ObjectId")] + [ValidateNotNullOrEmpty()] + [System.String]$ServicePrincipalId, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$Value, + + [Parameter(Mandatory = $true)] + [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] + [System.String]$Type, + + [Parameter(Mandatory = $true)] + [ValidateSet('Sign', 'Verify')] + [System.String]$Usage, + + [Parameter(Mandatory = $true)] + [System.String]$Proof, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.String]$CustomKeyIdentifier, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + [System.Nullable[System.DateTime]] $StartDate, + + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, + HelpMessage = "Specifies the end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + [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 + } + passwordCredential = $null + proof = $Proof + } + + if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + $params["passwordCredential"] = @{ + secretText = $PSBoundParameters["CustomKeyIdentifier"] + } + } + + 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) + + $response | ForEach-Object { + if ($null -ne $_) { + Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime + Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime + } + } + + $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: $($_.Exception.Message)" + } + } +} + From 795020b43165d2cd834a6d6d0eb7e28aebf0d637 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 08:31:03 +0300 Subject: [PATCH 11/21] refactor and add tests for New-EntraServicePrincipalKeyCredentials --- ...New-EntraServicePrincipalKeyCredential.ps1 | 22 +++--- ...EntraBetaServicePrincipalKeyCredential.ps1 | 25 +++--- ...traServicePrincipalKeyCredential.Tests.ps1 | 78 +++++++++++++++++++ 3 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index b4438a01a8..8c28568d96 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -1,35 +1,35 @@ function New-EntraServicePrincipalKeyCredential { [CmdletBinding()] param ( - [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [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 start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + HelpMessage = "Specifies the value for the key encoded in Base64.")] [System.String]$Value, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] [System.String]$Type, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the key usage.")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, - [Parameter(Mandatory = $true)] + [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 the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] - [System.String]$CustomKeyIdentifier, + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] + [System.String]$PasswordCredential, [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + 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 end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + HelpMessage = "Specifies the time when the key becomes invalid as a DateTime object.")] [System.Nullable[System.DateTime]] $EndDate ) @@ -61,6 +61,10 @@ function New-EntraServicePrincipalKeyCredential { } if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + Write-Error -Message $errorMessage -ErrorAction Stop + } $params["passwordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 078d0946e0..40f442c5c2 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -1,35 +1,35 @@ function New-EntraBetaServicePrincipalKeyCredential { [CmdletBinding()] param ( - [Parameter(Mandatory = $true, HelpMessage = "Specifies the unique identifier (ObjectId) of the service principal to which the password credential will be added.")] + [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 start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + HelpMessage = "Specifies the value for the key encoded in Base64.")] [System.String]$Value, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] [ValidateSet('AsymmetricX509Cert', 'X509CertAndPassword')] [System.String]$Type, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the key usage.")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, - [Parameter(Mandatory = $true)] + [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 the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] - [System.String]$CustomKeyIdentifier, + [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] + [System.String]$PasswordCredential, [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the start date and time (UTC) from which the password credential becomes valid. If not specified, defaults to the current date and time.")] + 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 end date and time (UTC) after which the password credential expires. If not specified, defaults to one year from the current date and time.")] + HelpMessage = "Specifies the time when the key becomes invalid as a DateTime object.")] [System.Nullable[System.DateTime]] $EndDate ) @@ -61,6 +61,10 @@ function New-EntraBetaServicePrincipalKeyCredential { } if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { + if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + Write-Error -Message $errorMessage -ErrorAction Stop + } $params["passwordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } @@ -96,5 +100,4 @@ function New-EntraBetaServicePrincipalKeyCredential { Write-Error "Failed to add key credential: $($_.Exception.Message)" } } -} - +} \ No newline at end of file diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 new file mode 100644 index 0000000000..ce33a27b86 --- /dev/null +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -0,0 +1,78 @@ +# ------------------------------------------------------------------------------ +# 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 + "displayName" = "Password friendly name" + "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 -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" + } + It "Should fail when ServicePrincipalId is empty" { + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "" -DisplayName "Helpdesk Secret" } | 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 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 fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "Missing an argument for parameter 'PasswordCredential'*" + } + + It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { + $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" -PasswordCredential "MySecretPassword" + $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-EntraServicePrincipalPasswordCredential" + $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" + $result | Should -Not -BeNullOrEmpty + $userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraServicePrincipalPasswordCredential" + 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-EntraServicePrincipalPasswordCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" -Debug } | Should -Not -Throw + } + finally { + # Restore original confirmation preference + $DebugPreference = $originalDebugPreference + } + } + } +} \ No newline at end of file From 3da088970538f5e0e933af0bdf71856b96d9bc36 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 09:14:57 +0300 Subject: [PATCH 12/21] refactors --- .../Applications/New-EntraServicePrincipalKeyCredential.ps1 | 4 ++-- .../New-EntraBetaServicePrincipalKeyCredential.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 8c28568d96..28b50cce8b 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -60,8 +60,8 @@ function New-EntraServicePrincipalKeyCredential { proof = $Proof } - if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { - if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ($Type -eq 'X509CertAndPassword') { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 40f442c5c2..377697cab4 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -60,8 +60,8 @@ function New-EntraBetaServicePrincipalKeyCredential { proof = $Proof } - if ($Type -eq 'X509CertAndPassword' -and $null -ne $PSBoundParameters["CustomKeyIdentifier"]) { - if (-not [string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ($Type -eq 'X509CertAndPassword') { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } From 487808358d4e96307df07d465850e023c0d879fd Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 11:53:57 +0300 Subject: [PATCH 13/21] refactors add tests --- ...New-EntraServicePrincipalKeyCredential.ps1 | 19 ++++++------------- ...EntraBetaServicePrincipalKeyCredential.ps1 | 18 +++++------------- ...traServicePrincipalKeyCredential.Tests.ps1 | 17 +++++++++-------- ...rvicePrincipalPasswordCredential.Tests.ps1 | 3 +++ 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 28b50cce8b..0e8be23fbf 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -7,7 +7,7 @@ function New-EntraServicePrincipalKeyCredential { [System.String]$ServicePrincipalId, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the value for the key encoded in Base64.")] + HelpMessage = "Specifies the value for the private key encoded in Base64.")] [System.String]$Value, [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] @@ -24,8 +24,7 @@ function New-EntraServicePrincipalKeyCredential { [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] [System.String]$PasswordCredential, - [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the time when the key becomes valid as a DateTime object.")] + [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, @@ -42,7 +41,6 @@ function New-EntraServicePrincipalKeyCredential { } } - PROCESS { $customHeaders = New-EntraCustomHeaders -Command $MyInvocation.MyCommand @@ -55,17 +53,19 @@ function New-EntraServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value + DateTimeStart = $StartDate + DateTimeEnd = $EndDate } passwordCredential = $null proof = $Proof } if ($Type -eq 'X509CertAndPassword') { - if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["PasswordCredential"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } - $params["passwordCredential"] = @{ + $params["PasswordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } } @@ -77,13 +77,6 @@ function New-EntraServicePrincipalKeyCredential { try { $response = Invoke-GraphRequest -Headers $customHeaders -Uri $URI -Method "POST" -Body ($params | ConvertTo-Json -Depth 4) - $response | ForEach-Object { - if ($null -ne $_) { - Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime - Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime - } - } - $targetTypeList = @() foreach ($data in $response) { $target = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 377697cab4..ecdc25faea 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -6,8 +6,7 @@ function New-EntraBetaServicePrincipalKeyCredential { [ValidateNotNullOrEmpty()] [System.String]$ServicePrincipalId, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the value for the key encoded in Base64.")] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies the value for the key encoded in Base64.")] [System.String]$Value, [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] @@ -42,7 +41,6 @@ function New-EntraBetaServicePrincipalKeyCredential { } } - PROCESS { $customHeaders = New-EntraBetaCustomHeaders -Command $MyInvocation.MyCommand @@ -55,17 +53,19 @@ function New-EntraBetaServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value + DateTimeStart = $StartDate + DateTimeEnd = $EndDate } passwordCredential = $null proof = $Proof } if ($Type -eq 'X509CertAndPassword') { - if ([string]::IsNullOrWhiteSpace($PSBoundParameters["CustomKeyIdentifier"])) { + if ([string]::IsNullOrWhiteSpace($PSBoundParameters["PasswordCredential"])) { $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" Write-Error -Message $errorMessage -ErrorAction Stop } - $params["passwordCredential"] = @{ + $params["PasswordCredential"] = @{ secretText = $PSBoundParameters["CustomKeyIdentifier"] } } @@ -76,14 +76,6 @@ function New-EntraBetaServicePrincipalKeyCredential { try { $response = Invoke-GraphRequest -Headers $customHeaders -Uri $URI -Method "POST" -Body ($params | ConvertTo-Json -Depth 4) - - $response | ForEach-Object { - if ($null -ne $_) { - Add-Member -InputObject $_ -MemberType AliasProperty -Name StartDate -Value StartDateTime - Add-Member -InputObject $_ -MemberType AliasProperty -Name EndDate -Value EndDateTime - } - } - $targetTypeList = @() foreach ($data in $response) { $target = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 index ce33a27b86..19922b2313 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -10,7 +10,6 @@ BeforeAll { $response = @{ "@odata.context" = 'https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.keyCredential' "customKeyIdentifier" = $null - "displayName" = "Password friendly name" "endDateTime" = "01/15/2027 14:22:00" "key" = $null "keyId" = "aaaaaaaa-0b0b-1c1c-2d2d-333333" @@ -27,10 +26,11 @@ Describe "New-EntraServicePrincipalKeyCredential" { Context "Test for New-EntraServicePrincipalKeyCredential" { It "Should fail when ServicePrincipalId is null" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" + { 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 "" -DisplayName "Helpdesk Secret" } | 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." + { 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 return created service principal key credential when ServicePrincipalId is valid and all required parameters are provided" { @@ -41,7 +41,7 @@ Describe "New-EntraServicePrincipalKeyCredential" { } It "Should fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "Missing an argument for parameter 'PasswordCredential'*" + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" } It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { @@ -52,22 +52,23 @@ Describe "New-EntraServicePrincipalKeyCredential" { } It "Should contain 'User-Agent' header" { - $userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion New-EntraServicePrincipalPasswordCredential" - $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" + $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-EntraServicePrincipalPasswordCredential" + $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-EntraServicePrincipalPasswordCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -DisplayName "Helpdesk Secret" -StartDate "01/15/2025 14:22:00" -Debug } | Should -Not -Throw + { 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 diff --git a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 index 219d76bd91..58e21061bf 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 @@ -30,9 +30,11 @@ Describe "New-EntraServicePrincipalPasswordCredential" { Should -Invoke -CommandName Invoke-MgGraphRequest -ModuleName Microsoft.Entra.Applications -Times 1 } + It "Should fail when ServicePrincipalId is empty" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" } + It "Should fail when ServicePrincipalId is invalid" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId "" -DisplayName "Helpdesk Secret" } | 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." } @@ -47,6 +49,7 @@ Describe "New-EntraServicePrincipalPasswordCredential" { $true } } + It "Should execute successfully without throwing an error" { # Disable confirmation prompts $originalDebugPreference = $DebugPreference From 3154ee1677a3696cf4324cccdd11a724b8a63f8b Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Thu, 12 Jun 2025 16:04:51 +0300 Subject: [PATCH 14/21] fix failing tests --- ...traServicePrincipalKeyCredential.Tests.ps1 | 5 +- ...taServicePrincipalKeyCredentials.Tests.ps1 | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 index 19922b2313..a510388dfd 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -28,7 +28,7 @@ Describe "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." } @@ -41,7 +41,7 @@ Describe "New-EntraServicePrincipalKeyCredential" { } It "Should fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" } It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { @@ -76,4 +76,5 @@ Describe "New-EntraServicePrincipalKeyCredential" { } } } + } \ No newline at end of file diff --git a/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 b/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 new file mode 100644 index 0000000000..8c794d54c6 --- /dev/null +++ b/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 @@ -0,0 +1,80 @@ +# ------------------------------------------------------------------------------ +# 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 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 fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { + { New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "test" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" + } + + It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { + $result = New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" -PasswordCredential "MySecretPassword" + $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 + } + } + } + +} \ No newline at end of file From 08c88dd1195dafcc7511c1f86711996a7ff46682 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Fri, 13 Jun 2025 08:44:13 +0300 Subject: [PATCH 15/21] fix response type entra beta key credential --- .../Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index ecdc25faea..ea470788cf 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -78,7 +78,7 @@ function New-EntraBetaServicePrincipalKeyCredential { $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 + $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 From 335a78019005accf2183bd58e729f12ea2842f6e Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Mon, 16 Jun 2025 10:56:51 +0300 Subject: [PATCH 16/21] restore add Password credential tests --- .../New-EntraServicePrincipalPasswordCredential.Tests.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 index 58e21061bf..219d76bd91 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalPasswordCredential.Tests.ps1 @@ -30,11 +30,9 @@ Describe "New-EntraServicePrincipalPasswordCredential" { Should -Invoke -CommandName Invoke-MgGraphRequest -ModuleName Microsoft.Entra.Applications -Times 1 } - It "Should fail when ServicePrincipalId is empty" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId -DisplayName "Helpdesk Secret" } | Should -Throw "Missing an argument for parameter 'ServicePrincipalId'*" } - It "Should fail when ServicePrincipalId is invalid" { { New-EntraServicePrincipalPasswordCredential -ServicePrincipalId "" -DisplayName "Helpdesk Secret" } | 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." } @@ -49,7 +47,6 @@ Describe "New-EntraServicePrincipalPasswordCredential" { $true } } - It "Should execute successfully without throwing an error" { # Disable confirmation prompts $originalDebugPreference = $DebugPreference From 8d45c2e65f23d619fc900a4c88a4277d6258fddd Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 18 Jun 2025 08:38:57 +0300 Subject: [PATCH 17/21] refactors --- .../New-EntraServicePrincipalKeyCredential.ps1 | 12 ++++++++---- ...-EntraBetaServicePrincipalKeyCredential.ps1 | 18 +++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 0e8be23fbf..5a5dbefea0 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -7,14 +7,14 @@ function New-EntraServicePrincipalKeyCredential { [System.String]$ServicePrincipalId, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, - HelpMessage = "Specifies the value for the private key encoded in Base64.")] + HelpMessage = "Specifies the value for the public key encoded in Base64.")] [System.String]$Value, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the type of the key.")] + [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 key usage.")] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the usage of the key credential (e.g., Sign, Verify).")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, @@ -23,6 +23,9 @@ function New-EntraServicePrincipalKeyCredential { [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] [System.String]$PasswordCredential, + + [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, @@ -55,6 +58,7 @@ function New-EntraServicePrincipalKeyCredential { key = $Value DateTimeStart = $StartDate DateTimeEnd = $EndDate + customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null proof = $Proof @@ -66,7 +70,7 @@ function New-EntraServicePrincipalKeyCredential { Write-Error -Message $errorMessage -ErrorAction Stop } $params["PasswordCredential"] = @{ - secretText = $PSBoundParameters["CustomKeyIdentifier"] + secretText = $PSBoundParameters["PasswordCredential"] } } diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index ea470788cf..8520187405 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -1,19 +1,20 @@ 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.")] + [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 key encoded in Base64.")] + [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 the key.")] + [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 key usage.")] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = "Specifies the usage of the key credential (e.g., Sign, Verify).")] [ValidateSet('Sign', 'Verify')] [System.String]$Usage, @@ -22,9 +23,11 @@ function New-EntraBetaServicePrincipalKeyCredential { [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] [System.String]$PasswordCredential, + + [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.")] + [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, @@ -55,6 +58,7 @@ function New-EntraBetaServicePrincipalKeyCredential { key = $Value DateTimeStart = $StartDate DateTimeEnd = $EndDate + customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null proof = $Proof @@ -66,7 +70,7 @@ function New-EntraBetaServicePrincipalKeyCredential { Write-Error -Message $errorMessage -ErrorAction Stop } $params["PasswordCredential"] = @{ - secretText = $PSBoundParameters["CustomKeyIdentifier"] + secretText = $PSBoundParameters["PasswordCredential"] } } From 5344dc50a677f46a523d002774c12c45fd9ae9cf Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 18 Jun 2025 08:41:43 +0300 Subject: [PATCH 18/21] refactors --- .../Applications/New-EntraServicePrincipalKeyCredential.ps1 | 4 ++-- .../New-EntraBetaServicePrincipalKeyCredential.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 5a5dbefea0..f7f0faa8fc 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -56,8 +56,8 @@ function New-EntraServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value - DateTimeStart = $StartDate - DateTimeEnd = $EndDate + dateTimeStart = $StartDate + dateTimeEnd = $EndDate customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 8520187405..ba5bc3b7c8 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -56,8 +56,8 @@ function New-EntraBetaServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value - DateTimeStart = $StartDate - DateTimeEnd = $EndDate + dateTimeStart = $StartDate + dateTimeEnd = $EndDate customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null From f05a8c85682d9836778ff47972a76c5fb02bb2e5 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 2 Jul 2025 12:11:53 +0300 Subject: [PATCH 19/21] work on PR feedbacks --- .../New-EntraServicePrincipalKeyCredential.ps1 | 13 ------------- ...New-EntraBetaServicePrincipalKeyCredential.ps1 | 13 ------------- ...w-EntraServicePrincipalKeyCredential.Tests.ps1 | 15 ++++----------- ...raBetaServicePrincipalKeyCredentials.Tests.ps1 | 15 ++++----------- 4 files changed, 8 insertions(+), 48 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index f7f0faa8fc..2219a216ca 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -21,9 +21,6 @@ function New-EntraServicePrincipalKeyCredential { [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 = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] - [System.String]$PasswordCredential, - [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies a custom identifier for the key credential.")] [System.String] $CustomKeyIdentifier, @@ -64,16 +61,6 @@ function New-EntraServicePrincipalKeyCredential { proof = $Proof } - if ($Type -eq 'X509CertAndPassword') { - if ([string]::IsNullOrWhiteSpace($PSBoundParameters["PasswordCredential"])) { - $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" - Write-Error -Message $errorMessage -ErrorAction Stop - } - $params["PasswordCredential"] = @{ - secretText = $PSBoundParameters["PasswordCredential"] - } - } - Write-Debug("============================ TRANSFORMATIONS ============================") $params.Keys | ForEach-Object { "$_ : $($params[$_])" } | Write-Debug Write-Debug("=========================================================================`n") diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index ba5bc3b7c8..bc066d71f8 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -21,9 +21,6 @@ function New-EntraBetaServicePrincipalKeyCredential { [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 = "Contain the password for the key. This property is required for keys of type X509CertAndPassword.")] - [System.String]$PasswordCredential, - [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Specifies a custom identifier for the key credential.")] [System.String] $CustomKeyIdentifier, @@ -64,16 +61,6 @@ function New-EntraBetaServicePrincipalKeyCredential { proof = $Proof } - if ($Type -eq 'X509CertAndPassword') { - if ([string]::IsNullOrWhiteSpace($PSBoundParameters["PasswordCredential"])) { - $errorMessage = "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" - Write-Error -Message $errorMessage -ErrorAction Stop - } - $params["PasswordCredential"] = @{ - secretText = $PSBoundParameters["PasswordCredential"] - } - } - Write-Debug("============================ TRANSFORMATIONS ============================") $params.Keys | ForEach-Object { "$_ : $($params[$_])" } | Write-Debug Write-Debug("=========================================================================`n") diff --git a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 index a510388dfd..5c0ba221a4 100644 --- a/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 +++ b/test/Entra/Applications/New-EntraServicePrincipalKeyCredential.Tests.ps1 @@ -33,19 +33,12 @@ Describe "New-EntraServicePrincipalKeyCredential" { { 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 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 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 fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { - { New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" - } - - It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { - $result = New-EntraServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" -PasswordCredential "MySecretPassword" + 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 diff --git a/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 b/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 index 8c794d54c6..cd3afbe163 100644 --- a/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 +++ b/test/EntraBeta/Applications/New-EntraBetaServicePrincipalKeyCredentials.Tests.ps1 @@ -33,19 +33,12 @@ Describe "New-EntraBetaServicePrincipalKeyCredential" { { 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 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 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 fail when key type is X509CertAndPassword and PasswordCredential hasn't been provided" { - { New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "test" } | Should -Throw "The 'CustomKeyIdentifier' property is required for keys of type X509CertAndPassword" - } - - It "Should return created service principal key credential when PasswordCredential is provided for key type X509CertAndPassword" { - $result = New-EntraBetaServicePrincipalKeyCredential -ServicePrincipalId "aaaaaaaa-2222-1111-1111-cccccccccccc" -Value "U29mdHdhcmU=" -Type "X509CertAndPassword" -Usage "Sign" -Proof "c29tZVByb29m" -PasswordCredential "MySecretPassword" + 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 From 3f27976a5b499b8ecd4da1269bd99477383a1f82 Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Wed, 2 Jul 2025 16:07:09 +0300 Subject: [PATCH 20/21] fix wrong date keys in KeyCredential --- .../New-EntraServicePrincipalKeyCredential.ps1 | 11 +++++------ .../New-EntraBetaServicePrincipalKeyCredential.ps1 | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index 2219a216ca..b575aea467 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -53,8 +53,8 @@ function New-EntraServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value - dateTimeStart = $StartDate - dateTimeEnd = $EndDate + startDateTime = $StartDate + endDateTime = $EndDate customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null @@ -64,7 +64,7 @@ function New-EntraServicePrincipalKeyCredential { 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) @@ -81,8 +81,7 @@ function New-EntraServicePrincipalKeyCredential { $targetTypeList } catch { - Write-Error "Failed to add key credential: $($_.Exception.Message)" + Write-Error "Failed to add key credential: $($_)" } } -} - +} \ No newline at end of file diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index bc066d71f8..5f98ca17c7 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -53,8 +53,8 @@ function New-EntraBetaServicePrincipalKeyCredential { type = $Type usage = $Usage key = $Value - dateTimeStart = $StartDate - dateTimeEnd = $EndDate + startDateTime = $StartDate + endDateTime = $EndDate customKeyIdentifier = $CustomKeyIdentifier } passwordCredential = $null @@ -80,7 +80,7 @@ function New-EntraBetaServicePrincipalKeyCredential { $targetTypeList } catch { - Write-Error "Failed to add key credential: $($_.Exception.Message)" + Write-Error "Failed to add key credential: $($_)" } } } \ No newline at end of file From d8fcae6c962424e57cdc4e910ee519c1e85eb25a Mon Sep 17 00:00:00 2001 From: Derrick Butoyi Date: Mon, 14 Jul 2025 08:57:16 +0300 Subject: [PATCH 21/21] add licence --- .../Applications/New-EntraServicePrincipalKeyCredential.ps1 | 5 +++++ .../New-EntraBetaServicePrincipalKeyCredential.ps1 | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 index b575aea467..7a650429dd 100644 --- a/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 +++ b/module/Entra/Microsoft.Entra/Applications/New-EntraServicePrincipalKeyCredential.ps1 @@ -1,3 +1,7 @@ +# ------------------------------------------------------------------------------ +# 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 ( @@ -64,6 +68,7 @@ function New-EntraServicePrincipalKeyCredential { 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) diff --git a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 index 5f98ca17c7..fd4d4d4952 100644 --- a/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 +++ b/module/EntraBeta/Microsoft.Entra.Beta/Applications/New-EntraBetaServicePrincipalKeyCredential.ps1 @@ -1,3 +1,7 @@ +# ------------------------------------------------------------------------------ +# 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 (