Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make domain suffix optional #10

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ package-lock.json
ZZBuild-Help.ps1
test1.ps1
helpdoc.ps1
StyleGuide.md
StyleGuide.md
.copilot/
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "PowerShell: Launch Current File",
"type": "PowerShell",
"request": "launch",
"script": "${file}",
"args": [],
"createTemporaryIntegratedConsole": true
}
]
}
17 changes: 10 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added Get-TkMsalToken cmdlet to retrieve an MSAL token using API calls.
- Added Managed Identity support for Get-TkMsalToken cmdlet (Needs to be tested).
- SecureString support for Get-TkMsalToken cmdlet.
- Formatting alignment for cmdlets.

### Fixed

- Fixed authentication context for MgGraph.
- Failing private test made generic.

### Changed

- Updated private function names to be more descriptive.
- Removed MSAL.PS dependency from Send-TkEmailAppMessage function.
- Removed Send-TkEmailAppMessage module install if manual parameters are provided.

## [0.2.1] - 2025-03-17

### Added

- Added Get-TkMsalToken cmdlet to retrieve an MSAL token using API calls.
- Added Managed Identity support for Get-TkMsalToken cmdlet (Needs to be tested).
- SecureString support for Get-TkMsalToken cmdlet.
- Formatting alignment for cmdlets.

## [0.2.0] - 2025-03-14

### Added
Expand Down
29 changes: 29 additions & 0 deletions Cmdlets_Mapping.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Function Name,Visibility,Used Cmdlets
Connect-TkMsService,Private,"Get-MgUser, Get-MgContext, Get-MgOrganization, Remove-MgContext, Connect-MgGraph, Disconnect-ExchangeOnline, Connect-ExchangeOnline"
ConvertTo-ParameterSplat,Private,Write-AuditLog
Get-TkExistingCert,Private,"Get-ChildItem, Where-Object, Remove-Item, Write-AuditLog"
Get-TkExistingSecret,Private,"Get-Secret, Write-AuditLog"
Get-TkMsalToken,Private,"Invoke-RestMethod, Write-AuditLog"
Initialize-TkAppAuthCertificate,Private,"Get-ChildItem, New-SelfSignedCertificate, Write-AuditLog"
Initialize-TkAppName,Private,Write-AuditLog
Initialize-TkEmailAppParamsObject,Private,
Initialize-TkM365AuditAppParamsObject,Private,
Initialize-TkMemPolicyManagerAppParamsObject,Private,
Initialize-TkModuleEnv,Private,"Install-Module, Import-Module, Get-Module, Write-AuditLog"
Initialize-TkRequiredResourcePermissionObject,Private,"Get-MgServicePrincipal, Find-MgGraphPermission, Write-AuditLog"
New-TkAppRegistration,Private,"Get-ChildItem, New-MgApplication, Write-AuditLog"
New-TkAppSpOauth2Registration,Private,Write-AuditLog
Connect-TkMsService,Private,"Get-MgUser, Get-MgContext, Get-MgOrganization, Remove-MgContext, Connect-MgGraph, Disconnect-ExchangeOnline, Connect-ExchangeOnline"
ConvertTo-ParameterSplat,Private,Write-AuditLog
Get-TkExistingCert,Public,"Get-ChildItem, Where-Object, Remove-Item, Write-AuditLog"
Get-TkExistingSecret,Public,"Get-Secret, Write-AuditLog"
Get-TkMsalToken,Public,"Invoke-RestMethod, Write-AuditLog"
Initialize-TkAppAuthCertificate,Public,"Get-ChildItem, New-SelfSignedCertificate, Write-AuditLog"
Initialize-TkAppName,Public,Write-AuditLog
Initialize-TkEmailAppParamsObject,Public,
Initialize-TkM365AuditAppParamsObject,Public,
Initialize-TkMemPolicyManagerAppParamsObject,Public,
Initialize-TkModuleEnv,Public,"Install-Module, Import-Module, Get-Module, Write-AuditLog"
Initialize-TkRequiredResourcePermissionObject,Public,"Get-MgServicePrincipal, Find-MgGraphPermission, Write-AuditLog"
New-TkAppRegistration,Public,"Get-ChildItem, New-MgApplication, Write-AuditLog"
New-TkAppSpOauth2Registration,Public,Write-AuditLog
7 changes: 4 additions & 3 deletions RequiredModules.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
ChangelogManagement = 'latest'
Sampler = 'latest'
'Sampler.GitHubTasks' = 'latest'


'Microsoft.Graph' = 'latest'
'ExchangeOnlineManagement' = 'latest'
'Microsoft.PowerShell.SecretManagement' = 'latest'
'SecretManagement.JustinGrote.CredMan' = 'latest'
}

6 changes: 6 additions & 0 deletions source/Private/Connect-TkMsService.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ function Connect-TkMsService {
[Switch]
$ExchangeOnline
)
# Used Cmdlets
# Get-MgUser, Get-MgContext, Get-MgOrganization, Remove-MgContext, Connect-MgGraph, Disconnect-ExchangeOnline, Connect-ExchangeOnline

# Begin Logging
if (-not $script:LogString) {
Write-AuditLog -Start
Expand Down Expand Up @@ -160,5 +163,8 @@ function Connect-TkMsService {
}
}
}
else {
Write-AuditLog 'No service specified for connection.'
}
Write-AuditLog -EndFunction
}
23 changes: 13 additions & 10 deletions source/Private/Get-TkExistingCert.ps1
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
<#
.SYNOPSIS
Retrieves an existing certificate from the current user's certificate store based on the provided certificate name.
Retrieves an existing certificate from the current user's certificate store based on the subject name.

.DESCRIPTION
The Get-TkExistingCert function searches for a certificate in the current user's "My" certificate store with a subject that matches the provided certificate name.
If the certificate is found, it logs audit messages and provides instructions for removing the certificate if needed.
If the certificate is not found, it logs an audit message indicating that the certificate does not exist.
The Get-TkExistingCert function searches for a certificate in the current user's certificate store with the specified subject name.
If the certificate exists, it provides instructions on how to remove the certificate and optionally removes it if confirmed by the user.

.PARAMETER CertName
The subject name of the certificate to search for in the current user's certificate store.
The subject name of the certificate to search for in the current user's certificate store.

.EXAMPLE
PS C:\> Get-TkExistingCert -CertName "CN=example.com"
This command searches for a certificate with the subject "CN=example.com" in the current user's certificate store.
PS C:\> Get-TkExistingCert -CertName "CN=example.com"
Searches for a certificate with the subject name "CN=example.com" in the current user's certificate store.
If found, it provides instructions on how to remove the certificate and optionally removes it if confirmed by the user.

.NOTES
Author: DrIOSx
Date: 2025-03-12
Version: 1.0
This function uses the certificate store path 'Cert:\CurrentUser\My' to search for the certificate.
The function logs its operations using the Write-AuditLog cmdlet.
#>
function Get-TkExistingCert {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
Expand Down
14 changes: 7 additions & 7 deletions source/Private/Get-TkExistingSecret.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ function Get-TkExistingSecret {
[string]$AppName,
[string]$VaultName = 'GraphEmailAppLocalStore'
)
Write-AuditLog -BeginFunction
if (-not $script:LogString) {
Write-AuditLog -Start
}
else {
Write-AuditLog -BeginFunction
}
try {
$ExistingSecret = Get-Secret -Name "$AppName" -Vault $VaultName -ErrorAction SilentlyContinue
if ($ExistingSecret) {
return $true
}
else {
return $false
}
return $null -ne $ExistingSecret
}
finally {
Write-AuditLog -EndFunction
Expand Down
2 changes: 1 addition & 1 deletion source/Private/Get-TkMsalToken.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function Get-TkMsalToken {
}
}
process {
if ($PSCmdlet.ParameterSetName -eq 'ManagedIdentity') {
if ($PSCmdlet.ParameterSetName -eq 'ManagedIdentity' -and $UseManagedIdentity) {
# Managed Identity Authentication (Only Works in Azure-hosted Environments)
try {
$uri = 'http://169.254.169.254/metadata/identity/oauth2/token?resource=https://graph.microsoft.com&api-version=2019-08-01'
Expand Down
5 changes: 2 additions & 3 deletions source/Public/Publish-TkEmailApp.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
1. Creating a new app with specified parameters.
2. Using an existing app and attaching a certificate to it.
.PARAMETER AppPrefix
The prefix used to initialize the Graph Email App. Must be 2-4 characters, letters, and numbers only. Default is 'Gtk'.
The prefix used to initialize the Graph Email App. Must be 2-4 characters, letters, and numbers only. The default value is 'Gtk'.
.PARAMETER AuthorizedSenderUserName
The username of the authorized sender. Must be a valid email address.
.PARAMETER MailEnabledSendingGroup
Expand All @@ -18,7 +18,7 @@
.PARAMETER CertThumbprint
The thumbprint of the certificate to be retrieved. Must be a valid 40-character hexadecimal string.
.PARAMETER KeyExportPolicy
Key export policy for the certificate. Valid values are 'Exportable' and 'NonExportable'. Default is 'NonExportable'.
Key export policy for the certificate. Valid values are 'Exportable' and 'NonExportable'. The default value is 'NonExportable'.
.PARAMETER VaultName
If specified, use a custom vault name. Otherwise, use the default 'GraphEmailAppLocalStore'.
.PARAMETER OverwriteVaultSecret
Expand Down Expand Up @@ -521,7 +521,6 @@ function Publish-TkEmailApp {
}
} # end switch
}

}
end {
if ($ReturnParamSplat -and $graphEmailApp) {
Expand Down
98 changes: 37 additions & 61 deletions tests/Unit/Private/Connect-TkMsService.tests.ps1
Original file line number Diff line number Diff line change
@@ -1,78 +1,54 @@
$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path
$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
$(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } )
}).BaseName
($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and
$(try { Test-ModuleManifest $_.FullName -ErrorAction Stop } catch { $false } )
}).BaseName

Import-Module $ProjectName

InModuleScope $ProjectName {
Describe 'Connect-TkMsService' {
BeforeAll {
# Mocks are now set up once for the entire Describe block
Mock -CommandName 'Write-AuditLog' -ModuleName GraphAppToolkit
Mock -CommandName 'Get-MgUser' -ModuleName GraphAppToolkit
Mock -CommandName 'Get-MgContext' -ModuleName GraphAppToolkit
Mock -CommandName 'Get-MgOrganization' -ModuleName GraphAppToolkit
Mock -CommandName 'Remove-MgContext' -ModuleName GraphAppToolkit
Mock -CommandName 'Connect-MgGraph' -ModuleName GraphAppToolkit
Mock -CommandName 'Get-OrganizationConfig' -ModuleName GraphAppToolkit
Mock -CommandName 'Disconnect-ExchangeOnline' -ModuleName GraphAppToolkit
Mock -CommandName 'Connect-ExchangeOnline' -ModuleName GraphAppToolkit
# Mock external cmdlets
function Get-MgUser { $false }
function Get-MgContext { }
function Connect-MgGraph { }
function Remove-MgContext { }
function Get-OrganizationConfig { }
function Connect-ExchangeOnline { }
function Disconnect-ExchangeOnline { }
Mock -CommandName 'Get-MgUser' -MockWith { @{} }
Mock -CommandName 'Get-MgContext' -MockWith { @{ Scopes = @('User.Read') } }
Mock -CommandName 'Connect-MgGraph' -MockWith { }
Mock -CommandName 'Remove-MgContext' -MockWith { }
Mock -CommandName 'Get-OrganizationConfig' -MockWith { @{} }
Mock -CommandName 'Connect-ExchangeOnline' -MockWith { }
Mock -CommandName 'Disconnect-ExchangeOnline' -MockWith { }
}

Context 'When connecting to Microsoft Graph' {
It 'Should connect to Microsoft Graph with specified scopes' {
$params = @{
MgGraph = $true
GraphAuthScopes = @('User.Read', 'Mail.Read')
}

Connect-TkMsService @params

Assert-MockCalled -CommandName 'Connect-MgGraph' -ModuleName GraphAppToolkit -Exactly -Times 1
Assert-MockCalled -CommandName 'Write-AuditLog' -ModuleName GraphAppToolkit -Exactly -Times 1 -Scope It -ParameterFilter { $_ -eq 'Connected to Microsoft Graph.' }
}

It 'Should reuse existing Microsoft Graph session if valid' {
Mock -CommandName 'Get-MgUser' -ModuleName GraphAppToolkit -MockWith { }
Mock -CommandName 'Get-MgContext' -ModuleName GraphAppToolkit -MockWith { @{ Scopes = @('User.Read', 'Mail.Read') } }

$params = @{
MgGraph = $true
GraphAuthScopes = @('User.Read', 'Mail.Read')
}

Connect-TkMsService @params

Assert-MockCalled -CommandName 'Get-MgUser' -ModuleName GraphAppToolkit -Exactly -Times 1
Assert-MockCalled -CommandName 'Write-AuditLog' -ModuleName GraphAppToolkit -Exactly -Times 1 -Scope It -ParameterFilter { $_ -eq 'Using existing Microsoft Graph session.' }
It 'Connects to Microsoft Graph when -MgGraph is specified' {
Connect-TkMsService -MgGraph -GraphAuthScopes 'User.Read' -Confirm:$false
Assert-MockCalled -CommandName 'Connect-MgGraph' -Exactly -Times 1
}
}

Context 'When connecting to Exchange Online' {
It 'Should connect to Exchange Online' {
$params = @{
ExchangeOnline = $true
}

Connect-TkMsService @params

Assert-MockCalled -CommandName 'Connect-ExchangeOnline' -ModuleName GraphAppToolkit -Exactly -Times 1
Assert-MockCalled -CommandName 'Write-AuditLog' -ModuleName GraphAppToolkit -Exactly -Times 1 -Scope It -ParameterFilter { $_ -eq 'Connected to Exchange Online.' }
It 'Connects to Exchange Online when -ExchangeOnline is specified' {
Connect-TkMsService -ExchangeOnline -Confirm:$false
Assert-MockCalled -CommandName 'Connect-ExchangeOnline' -Exactly -Times 1
}

It 'Should reuse existing Exchange Online session if valid' {
Mock -CommandName 'Get-OrganizationConfig' -ModuleName GraphAppToolkit -MockWith { }

$params = @{
ExchangeOnline = $true
}

Connect-TkMsService @params

Assert-MockCalled -CommandName 'Get-OrganizationConfig' -ModuleName GraphAppToolkit -Exactly -Times 1
Assert-MockCalled -CommandName 'Write-AuditLog' -ModuleName GraphAppToolkit -Exactly -Times 1 -Scope It -ParameterFilter { $_ -eq 'Using existing Exchange Online session.' }
}
Context 'When connecting to both services' {
It 'Connects to both Microsoft Graph and Exchange Online when both switches are specified' {
Connect-TkMsService -MgGraph -GraphAuthScopes 'User.Read' -ExchangeOnline -Confirm:$false
Assert-MockCalled -CommandName 'Connect-MgGraph' -Exactly -Times 1
Assert-MockCalled -CommandName 'Connect-ExchangeOnline' -Exactly -Times 1
}
}
Context 'When no switches are specified' {
It 'Does not connect to any service when no switches are specified' {
Connect-TkMsService -Confirm:$false
Assert-MockCalled -CommandName 'Connect-MgGraph' -Exactly -Times 0
Assert-MockCalled -CommandName 'Connect-ExchangeOnline' -Exactly -Times 0
}
}
}
Expand Down
61 changes: 5 additions & 56 deletions tests/Unit/Private/ConvertTo-ParameterSplat.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,13 @@ $ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{
}).BaseName



Import-Module $ProjectName

InModuleScope $ProjectName {
Describe 'ConvertTo-ParameterSplat' {
It 'should convert object properties to a parameter splatting hashtable script' {
$obj = [PSCustomObject]@{ Name = 'John'; Age = 30 }
$expected = @'
$params = @{
Name = "John"
Age = 30
}
'@
$result = $obj | ConvertTo-ParameterSplat
$result | Should -BeExactly $expected
}
It 'should handle string properties correctly' {
$obj = [PSCustomObject]@{ City = 'New York'; Country = 'USA' }
$expected = @'
$params = @{
City = "New York"
Country = "USA"
}
'@
$result = $obj | ConvertTo-ParameterSplat
$result | Should -BeExactly $expected
}
It 'should handle numeric properties correctly' {
$obj = [PSCustomObject]@{ Width = 1920; Height = 1080 }
$expected = @'
$params = @{
Width = 1920
Height = 1080
}
'@
$result = $obj | ConvertTo-ParameterSplat
$result | Should -BeExactly $expected
}
It 'should handle mixed property types correctly' {
$obj = [PSCustomObject]@{ Name = 'Alice'; Age = 25; IsActive = $true }
$expected = @'
$params = @{
Name = "Alice"
Age = 25
IsActive = True
}
'@
$result = $obj | ConvertTo-ParameterSplat
$result | Should -BeExactly $expected
}
It 'should handle empty objects correctly' {
$obj = [PSCustomObject]@{}
$expected = @'
$params = @{
}
'@
$result = $obj | ConvertTo-ParameterSplat
$result | Should -BeExactly $expected
Describe ConvertTo-ParameterSplat {
It "Should exist" {
Test-Path function:\ConvertTo-ParameterSplat | Should -Be $true
}
}
}

}
Loading