diff --git a/CHANGELOG.md b/CHANGELOG.md index 760f0d8..5bd3fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## Integration boundary review (unreleased) + +- Archive Close-AzureSession in Legacy; document its missing Test-AzureSession dependency. +- Remove the process-wide certificate-validation bypass snippet; retain Git history. +- Remove legacy New-ExchangeSession and Close-ExchangeSession; retain their Git history. +- Delegate Exchange Online connection management to ExchangeOnlineManagement. +- Keep WinSCP/SCP and PGP/OpenPGP assigned to their own module projects. +- Replace the stale staging placeholder and correct README test coverage. +- Add isolated import/reload regression coverage for TLS, sessions and archive exclusion. +- Retain the 29-command supported API and alpha1 preview version. + ## Script-context helper restoration (unreleased) - Restore Get-ScriptDirectory and Get-ScriptName; export twenty-nine commands. diff --git a/Staging/Close-AzureSession.ps1 b/Legacy/Close-AzureSession.ps1 similarity index 90% rename from Staging/Close-AzureSession.ps1 rename to Legacy/Close-AzureSession.ps1 index f985e74..96f62ba 100644 --- a/Staging/Close-AzureSession.ps1 +++ b/Legacy/Close-AzureSession.ps1 @@ -1,3 +1,7 @@ +# Historical AzureAD implementation; not loaded or exported by IT-ToolBox v3. +# Calls the absent Test-AzureSession helper. Do not use as a working v3 command. +# See docs/Integrations.md for explicit Microsoft Graph/Az session ownership. + function Close-AzureSession { <# diff --git a/Legacy/Close-ExchangeSession.ps1 b/Legacy/Close-ExchangeSession.ps1 deleted file mode 100644 index e27862a..0000000 --- a/Legacy/Close-ExchangeSession.ps1 +++ /dev/null @@ -1,221 +0,0 @@ -function Close-ExchangeSession -{ - <# - .SYNOPSIS - Closes an open Exchange session. - - .DESCRIPTION - Function is used to close an open session to a remote Exchange server. - - Sessions can be specified by ID or Name, by default all sessions with a ConfigurationName of Microsoft.Exchange will be returned and removed. - - Office 365 sessions will not be removed - - .PARAMETER SessionId - An integer value representing the Sesison ID to remove. - - .PARAMETER SessionName - A string value representing the Session Name to remove. - - .PARAMETER Online - When parameter is specified function will only close any open Exchange Online session. - - .EXAMPLE - PS C:\> Close-ExchangeSession - - .OUTPUTS - System.Boolean - - .NOTES - If no SessionName or SessionId parameter is used function will close any Exchange remote session. -#> - - [CmdletBinding(DefaultParameterSetName = 'ReturnAllSessions')] - param - ( - [Parameter(ParameterSetName = 'SessionById')] - [ValidateNotNullOrEmpty()] - [int] - $SessionId, - [Parameter(ParameterSetName = 'SessionByName')] - [ValidateNotNullOrEmpty()] - [string] - $SessionName, - [Parameter(ParameterSetName = 'ReturnAllSessions')] - [Parameter(ParameterSetName = 'SessionById')] - [switch] - $Online - ) - - Begin - { - # Get current configuration - [string]$currentConfig = $ErrorActionPreference - - # Set error action preference - $ErrorActionPreference = 'Stop' - } - - Process - { - switch ($PsCmdlet.ParameterSetName) - { - 'SessionById' - { - try - { - # Check we have a valid session - [System.Management.Automation.Runspaces.PSSession]$sessionObject = Get-PSSession -Id $SessionId - [string]$sessionConfiguration = $sessionObject.ConfigurationName - - # Check session is the correct type - if ($sessionConfiguration -like '*Exchange*') - { - $paramRemovePSSession = @{ - Session = $sessionObject - Confirm = $false - } - - Remove-PSSession @paramRemovePSSession - - return $true - } - else - { - Write-Warning -Message "Session $SessionId is not an Exchange session" - - return $false - } - } - catch - { - Write-Warning -Message "No PsSession with ID $SessionId found!" - - return $false - } - - break - } - 'SessionByName' - { - try - { - # Get session by name - [System.Management.Automation.Runspaces.PSSession]$sessionObject = Get-PSSession -Name $SessionName - [string]$sessionConfiguration = $sessionObject.ConfigurationName - [string]$sessionComputerName = $sessionObject.ComputerName - - if ($PSBoundParameters.ContainsKey('Online')) - { - if ($sessionConfiguration -like '*Exchange*') - { - $paramRemovePSSession = @{ - Session = $sessionObject - Confirm = $false - } - - Remove-PSSession @paramRemovePSSession - - return $true - } - else - { - Write-Warning -Message "Session $SessionId is not a valid Exchange session!" - - return $false - } - } - else - { - # Check session is the correct type - if (($sessionConfiguration -like '*Exchange*') -and - ($sessionComputerName -notlike '*Office365*')) - { - $paramRemovePSSession = @{ - Session = $sessionObject - Confirm = $false - } - - Remove-PSSession @paramRemovePSSession - - return $true - } - else - { - Write-Warning -Message "Session $SessionId is not a valid Exchange session!" - - return $false - } - } - } - - catch - { - Write-Warning -Message "No PsSession with Name $SessionName found!" - - return $false - } - - break - } - 'ReturnAllSessions' - { - # Get all seesions - [array]$psSessions = Get-PSSession - - # Cycle through sessions - foreach ($session in $psSessions) - { - # Get configuration name - [string]$sessionConfiguration = $session.ConfigurationName - [string]$sessionId = $sessionObject.Id - [string]$sessionComputerName = $sessionObject.ComputerName - - try - { - if ($PSBoundParameters.ContainsKey('Online')) - { - if ($sessionConfiguration -like '*Exchange*') - { - $paramRemovePSSession = @{ - Session = $sessionObject - Confirm = $false - } - - Remove-PSSession @paramRemovePSSession - } - else - { - # Check session is the correct type - if (($sessionConfiguration -like '*Exchange*') -and - ($sessionComputerName -notlike '*Office365*')) - { - $paramRemovePSSession = @{ - Session = $session - Confirm = $false - } - - Remove-PSSession @paramRemovePSSession - } - } - } - } - catch - { - Write-Warning -Message "Session $SessionName and ID $sessionId could not be removed" - - return $false - } - - break - } - } - } - - End - { - # Revert back setting - $ErrorActionPreference = $currentConfig - } - } -} \ No newline at end of file diff --git a/Legacy/New-ExchangeSession.ps1 b/Legacy/New-ExchangeSession.ps1 deleted file mode 100644 index 7f540a4..0000000 --- a/Legacy/New-ExchangeSession.ps1 +++ /dev/null @@ -1,259 +0,0 @@ -function New-ExchangeSession -{ -<# - .SYNOPSIS - Creates a remote session to an on-prem Exchange Server. - - .DESCRIPTION - Simple function to create an import a new PowerShell sesssion to a on-prem Exchange Server. - - If an open session to an Exchange server is already found a warning is printed on screen and no further action is taken unless the -Force parameter is specified. - - .PARAMETER ServerName - A string representing the Exchange server name. - - .PARAMETER UseHttp - When parameter is specified it will force a connection over HTTP rather than HTTPS. - - Switch is intended for use in all situations where server certificate is not trusted or an HTTPS connection cannot be correctly completed. - - .PARAMETER AuthenticationProtocol - A string representing the authentication protocol to use while connecting to an on-prem Exchange server. - - If not specified Kerberos will be used by default. - - .PARAMETER AskCredentials - When parameter is specified user will be prompted to enter credentials. - - If not specified it will default to currently logged on user credentials. - - .PARAMETER Force - When used checks on open connections will be skipped function will try to establish a new one. - - Use with caution as this will cause cmdlets clobber. - - .PARAMETER UserName - A string representing the username to use for authentication with the remote Exchange server. - - .PARAMETER UserPassword - A clear text string representing the password to use for authentication with the remote Exchange server. - - .PARAMETER SecurePassword - A securestring object representing the password to user for authentication with the remote Exchange server. - - .EXAMPLE - PS C:\> New-ExchangeConnection - - .OUTPUTS - object, object - - .NOTES - By default HTTPS and Kerberos will be used to establish the connection. -#> - - [CmdletBinding(DefaultParameterSetName = 'AskCredentials', - HelpUri = 'https://PsCustomObject.github.io', - SupportsPaging = $false, - SupportsShouldProcess = $false)] - [OutputType([object], ParameterSetName = 'AskCredentials')] - [OutputType([object], ParameterSetName = 'UserNamePassword')] - [OutputType([object], ParameterSetName = 'UserNameSecurePassword')] - [OutputType([object])] - param - ( - [Parameter(ParameterSetName = 'AskCredentials', - Mandatory = $true)] - [Parameter(ParameterSetName = 'UserNamePassword')] - [Parameter(ParameterSetName = 'UserNameSecurePassword', - Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $ServerName, - [Parameter(ParameterSetName = 'AskCredentials')] - [Parameter(ParameterSetName = 'UserNamePassword')] - [Parameter(ParameterSetName = 'UserNameSecurePassword')] - [switch] - $UseHttp, - [Parameter(ParameterSetName = 'AskCredentials')] - [Parameter(ParameterSetName = 'UserNamePassword')] - [Parameter(ParameterSetName = 'UserNameSecurePassword')] - [ValidateSet('Basic', 'Credssp', 'Default', 'Digest', 'Kerberos', 'Negotiate', 'NegotiateWithImplicitCredential', IgnoreCase = $true)] - [Alias('Protocol')] - [string] - $AuthenticationProtocol = 'Kerberos', - [Parameter(ParameterSetName = 'AskCredentials')] - [switch] - $AskCredentials, - [Parameter(ParameterSetName = 'AskCredentials')] - [Parameter(ParameterSetName = 'UserNamePassword')] - [Parameter(ParameterSetName = 'UserNameSecurePassword')] - [switch] - $Force, - [Parameter(ParameterSetName = 'UserNamePassword', - Mandatory = $true)] - [Parameter(ParameterSetName = 'UserNameSecurePassword', - Mandatory = $true)] - [string] - $UserName, - [Parameter(ParameterSetName = 'UserNamePassword', - Mandatory = $true)] - [string] - $UserPassword, - [Parameter(ParameterSetName = 'UserNameSecurePassword', - Mandatory = $true, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true)] - [securestring] - $SecurePassword - ) - - Begin - { - # Get current configuration - [string]$currentConfig = $ErrorActionPreference - - # Set error action preference - $ErrorActionPreference = 'Stop' - - # Define conneciton URI - [string]$ConnectionUri = 'https://' - - # Define session command - $paramNewPSSession = @{ - ConfigurationName = 'Microsoft.Exchange' - } - } - Process - { - switch ($PSCmdlet.ParameterSetName) - { - 'AskCredentials' - { - # Get credentials from user - [System.Management.Automation.PSCredential]$AskCredentials = Get-Credential - - # Add to command splat - $paramNewPSSession.Add('Credential', $AskCredentials) - - break - } - 'UserNamePassword' - { - # Convert password to securestring - $paramConvertToSecureString = @{ - String = $UserPassword - AsPlainText = $true - Force = $true - } - - [securestring]$SecurePassword = ConvertTo-SecureString @paramConvertToSecureString - - # Create the credential object - $paramNewObject = @{ - TypeName = 'System.Management.Automation.PSCredential' - ArgumentList = ($UserName, $SecurePassword) - } - - [pscredential]$UserCredential = New-Object @paramNewObject - - # Add to command splat - $paramNewPSSession.Add('Credential', $UserCredential) - - break - } - 'UserNameSecurePassword' - { - # Create the credential object - $paramNewObject = @{ - TypeName = 'System.Management.Automation.PSCredential' - ArgumentList = ($UserName, $SecurePassword) - } - - [pscredential]$UserCredential = New-Object @paramNewObject - - # Add to command splat - $paramNewPSSession.Add('Credential', $UserCredential) - - break - } - } - - if ($PSBoundParameters.ContainsKey('Force')) - { - Write-Verbose -Message 'The Force paramter has been specified - Ignoring open sessions' - } - else - { - # Check if a session is already open - [array]$CurrentSessions = Get-PSSession - - if (($CurrentSessions.ConfigurationName -like '*Exchange*') -and - ($CurrentSessions.State -eq 'Opened') -and - ($CurrentSessions.ComputerName -notlike '*Office365*')) - { - # Get total session count - [int]$sessionsCount = $CurrentSessions.Count - - Write-Warning -Message "$sessionsCount open session(s) have been found" - Write-Warning -Message 'Use the -Force paramter to ignore or the -Verbose parameter for more details' - - if ($PSBoundParameters.ContainsKey('Verbose')) - { - foreach ($session in $CurrentSessions) - { - # Get session details - [string]$SessionName = $session.ComputerName - [string]$SessionId = $session.Id - [string]$SessionStatus = $session.State - - Write-Verbose -Message "Session Name: $SessionName - Session ID: $SessionId - Session Status: $SessionStatus" - } - } - - return - } - } - - switch ($PSBoundParameters.Keys) - { - 'UseHttp' - { - # Define connection URI protocol - [string]$ConnectionUri = 'http://' - - break - } - 'AuthenticationProtocol' - { - # User custom authentication protocol - [string]$AuthenticationProtocol = $AuthenticationProtocol - - # Add to command splat - $paramNewPSSession.Add('Authentication', $AuthenticationProtocol) - - break - } - 'AskCredentials' - { - # Get credentials from user - [System.Management.Automation.PSCredential]$AskCredentials = Get-Credential - - # Add to command splat - $paramNewPSSession.Add('Credential', $AskCredentials) - - break - } - } - - # Fill up connection URL - $ConnectionUri += "$ServerName/PowerShell" - - # Add to command splat - $paramNewPSSession.Add('ConnectionUri', $ConnectionUri) - - # Create new session - [System.Management.Automation.Runspaces.PSSession]$exSession = New-PSSession @paramNewPSSession - - return $exSession - } -} \ No newline at end of file diff --git a/Legacy/README.md b/Legacy/README.md index 6f85d05..099bab3 100644 --- a/Legacy/README.md +++ b/Legacy/README.md @@ -1,8 +1,16 @@ # Historical commands -These four commands are preserved for reference and are not loaded or exported by v3. -The string encryption design is unsuitable for new security implementations. -Exchange helpers require a separate compatibility review. +These three commands are reference implementations, not a supported compatibility +layer. They are never loaded or exported by v3. -Get-ScriptDirectory and Get-ScriptName now have supported implementations in Public/. -See the root README for caller resolution and interactive behavior. +| Command | Status | +| --- | --- | +| New-StringEncryption | Historical unauthenticated encryption; use only to migrate existing data in a separate session | +| New-StringDecryption | Historical decryption; original passphrase/salt/vector required | +| Close-AzureSession | AzureAD dependency and absent Test-AzureSession helper | + +See [the integration review](../docs/Integrations.md) before planning a replacement. +The root README documents supported encryption and script-context replacements. + +The former Exchange session wrappers have been removed. Their source remains in Git +history; use ExchangeOnlineManagement directly for Exchange Online operations. diff --git a/README.md b/README.md index fe04b95..8a6682d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ enterprise administration. Version 3 modernizes the module in small, tested step ## Status and requirements -This is the **3.0.0-alpha1 foundation**, not the completed modernization release. +This is the **3.0.0-alpha1 modernization preview**, not the completed modernization release. Requires PowerShell 7.4 or later (Core edition). Windows PowerShell 5.1 is not supported. The foundation imports without WinSCP, GnuPG, Active Directory or Exchange dependencies. @@ -68,9 +68,9 @@ Redaction is opt-in and does not guarantee detection of every secret. - SCP and GnuPG wrappers and bundled WinSCP binaries are removed. Separate modules will own file transfer and OpenPGP; no replacement is bundled here. -- `Legacy/` retains string encryption and Exchange helpers for reference. +- `Legacy/` retains historical string encryption and AzureAD helpers for reference. - All commands formerly retained in `Staging/v3/` now have supported implementations. - Its README records the migration; separate legacy/staged integrations remain excluded. They are not currently exported. + Its README records the migration; historical service integrations remain excluded. - Only the twenty-nine listed commands are exported. Private helpers, variables and aliases are not exported. Existing calls to other v2 commands require the v2 release until those commands return to the supported API. @@ -86,8 +86,8 @@ Invoke-Pester ./Tests CI runs syntax validation, isolated import and Pester on Windows, Linux and macOS using each hosted runner's installed PowerShell. It does not test every PowerShell release. Logger tests exercise module import, redaction, failed-write retention, default paths, -and simultaneous direct/buffered file writes from three processes. Windows/AD -integration tests are future work. +and simultaneous direct/buffered file writes from three processes. Temporary HKCU registry tests run on Windows. Live AD, remote CIM and service +authentication integration tests are future work. The logger is adopted from `PowerShell-Functions/New-LogEntry` at commit `d5a9edd`. The integrated logger includes the maintenance fixes described in CHANGELOG.md. @@ -399,3 +399,16 @@ Wildcard characters are treated literally. Non-filesystem provider paths and pat without a filename throw; no existence, extension or file-type check is performed. Unlike the legacy implementations, these helpers do not depend on script-scoped MyInvocation or silently return a module filename. + +## Service integration boundaries + +WinSCP/SCP and PGP/OpenPGP are separate module projects. No transfer backend or PGP +backend is bundled into this module; AES-GCM string encryption remains independent. + +The AzureAD helper is historical source, not a supported session-management command. +The legacy Exchange wrappers have been removed; use ExchangeOnlineManagement +directly for Exchange Online. The process-wide certificate-bypass snippet has been +removed. Import regression tests verify that loading and reloading IT-ToolBox leaves +TLS callbacks, service sessions and caller preferences alone, and excludes archived +and staged code. See [the integration review](./docs/Integrations.md) for defects, +service-specific migration directions and requirements for future wrappers. diff --git a/Staging/Enable-SelfSignedCertificate.ps1 b/Staging/Enable-SelfSignedCertificate.ps1 deleted file mode 100644 index b318436..0000000 --- a/Staging/Enable-SelfSignedCertificate.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) -{ - [string]$certCallback = @" - using System; - using System.Net; - using System.Net.Security; - using System.Security.Cryptography.X509Certificates; - public class ServerCertificateValidationCallback - { - public static void Ignore() - { - if(ServicePointManager.ServerCertificateValidationCallback ==null) - { - ServicePointManager.ServerCertificateValidationCallback += - delegate - ( - Object obj, - X509Certificate certificate, - X509Chain chain, - SslPolicyErrors errors - ) - { - return true; - }; - } - } - } -"@ - - Add-Type $certCallback -} \ No newline at end of file diff --git a/Staging/README.md b/Staging/README.md index 384409d..f19b417 100644 --- a/Staging/README.md +++ b/Staging/README.md @@ -1,3 +1,12 @@ -# About the folder - -Folder is used a staging area for code snippets and functions still under development, before push to master branch it will be deleted so nothing important should be stored under this folder. \ No newline at end of file +# Staging status + +There are no executable candidates in this directory. All former v3 utility +candidates have supported implementations in Public/. + +Close-AzureSession is archived in Legacy/ because it uses AzureAD and calls the +absent Test-AzureSession helper. The global certificate-validation bypass snippet +has been removed; its historical source remains available in Git history. + +Future candidates belong on a feature branch with tests before entering Public/. +Staging and Legacy are never loaded by the module. See +[the integration review](../docs/Integrations.md) for the remaining service boundaries. diff --git a/Staging/v3/README.md b/Staging/v3/README.md index 53306e4..dd7d8bd 100644 --- a/Staging/v3/README.md +++ b/Staging/v3/README.md @@ -4,4 +4,4 @@ All former candidates in this directory now have supported implementations in Public with tests and documented compatibility changes. This directory contains no commands and is not loaded by the module. -Legacy commands and the separate parent Staging integrations remain excluded. +The remaining historical integrations are documented in Legacy/ and docs/Integrations.md. diff --git a/Tests/IntegrationBoundary.Tests.ps1 b/Tests/IntegrationBoundary.Tests.ps1 new file mode 100644 index 0000000..dfc3c27 --- /dev/null +++ b/Tests/IntegrationBoundary.Tests.ps1 @@ -0,0 +1,73 @@ +BeforeAll { + $sourceRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path + $fixtureRoot = Join-Path $TestDrive 'isolated-module' + $null = New-Item -ItemType Directory -Path $fixtureRoot + foreach ($item in @('IT-ToolBox.psd1', 'IT-ToolBox.psm1', 'Public', 'Private', 'Legacy', 'Staging')) { + Copy-Item -LiteralPath (Join-Path $sourceRoot $item) -Destination $fixtureRoot -Recurse + } + # Fail loudly if a future loader starts executing either excluded directory. + Set-Content -LiteralPath (Join-Path $fixtureRoot 'Legacy/ImportBoundaryProbe.ps1') -Value "throw 'Legacy code must not execute during import.'" + Set-Content -LiteralPath (Join-Path $fixtureRoot 'Staging/ImportBoundaryProbe.ps1') -Value "throw 'Staging code must not execute during import.'" + $fixtureManifest = (Join-Path $fixtureRoot 'IT-ToolBox.psd1').Replace("'", "''") + $executable = Join-Path $PSHOME $(if ($IsWindows) { 'pwsh.exe' } else { 'pwsh' }) +} + +Describe 'Service integration import boundary' { + It 'preserves caller state on ' -ForEach @( + @{ Mode = 'initial import'; ImportCount = 1 } + @{ Mode = 'forced reload'; ImportCount = 2 } + ) { + $code = @' +$global:integrationCalls = 0 +foreach ($name in @( + 'Get-PSSession', 'New-PSSession', 'Remove-PSSession', 'Import-PSSession', + 'Connect-AzureAD', 'Disconnect-AzureAD', 'Test-AzureSession', + 'Connect-MgGraph', 'Disconnect-MgGraph', 'Get-MgContext', + 'Connect-AzAccount', 'Disconnect-AzAccount', 'Clear-AzContext', + 'Connect-ExchangeOnline', 'Disconnect-ExchangeOnline', 'Install-Module' +)) { + Set-Item -Path ('Function:global:' + $name) -Value { + $global:integrationCalls++ + throw 'Import must not manage service connections or dependencies.' + } +} +$ErrorActionPreference = 'Continue' +$WarningPreference = 'Continue' +$originalCallback = [Net.ServicePointManager]::ServerCertificateValidationCallback +$sentinel = [Net.Security.RemoteCertificateValidationCallback]{ param($sender, $certificate, $chain, $errors) return $false } +[Net.ServicePointManager]::ServerCertificateValidationCallback = $sentinel +try { + $location = (Get-Location).Path + for ($i = 0; $i -lt __IMPORT_COUNT__; $i++) { + $module = Import-Module '__MANIFEST__' -Force -PassThru -ErrorAction Stop + } + [pscustomobject]@{ + ServiceCalls = $global:integrationCalls + TlsCallbackPreserved = [object]::ReferenceEquals($sentinel, [Net.ServicePointManager]::ServerCertificateValidationCallback) + BypassTypeAbsent = $null -eq ('ServerCertificateValidationCallback' -as [type]) + ErrorPreference = [string]$ErrorActionPreference + WarningPreference = [string]$WarningPreference + LocationPreserved = $location -eq (Get-Location).Path + ExportCount = $module.ExportedFunctions.Count + HistoricalExports = @($module.ExportedFunctions.Keys | Where-Object { $_ -in @('Close-AzureSession', 'New-ExchangeSession', 'Close-ExchangeSession', 'Enable-SelfSignedCertificate') }).Count + } | ConvertTo-Json -Compress +} +finally { + [Net.ServicePointManager]::ServerCertificateValidationCallback = $originalCallback +} +'@ + $code = $code.Replace('__MANIFEST__', $fixtureManifest).Replace('__IMPORT_COUNT__', [string]$ImportCount) + $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code)) + $output = & $executable -NoProfile -NonInteractive -EncodedCommand $encoded + $LASTEXITCODE | Should -Be 0 + $result = $output | ConvertFrom-Json + $result.ServiceCalls | Should -Be 0 + $result.TlsCallbackPreserved | Should -BeTrue + $result.BypassTypeAbsent | Should -BeTrue + $result.ErrorPreference | Should -Be 'Continue' + $result.WarningPreference | Should -Be 'Continue' + $result.LocationPreserved | Should -BeTrue + $result.ExportCount | Should -Be 29 + $result.HistoricalExports | Should -Be 0 + } +} diff --git a/docs/Integrations.md b/docs/Integrations.md new file mode 100644 index 0000000..9505338 --- /dev/null +++ b/docs/Integrations.md @@ -0,0 +1,63 @@ +# Integration review and ownership + +IT-ToolBox v3 exports 29 utility commands. Importing the module must not connect, +disconnect, install external modules, change certificate validation, or manage a +caller's existing service sessions. Get-ReportChain and remote Get-OsUpTime retain +their documented optional dependencies when explicitly invoked. + +## Current boundaries + +| Integration | v3 disposition | Supported direction | +| --- | --- | --- | +| SCP / WinSCP | Excluded | Dedicated transfer module; no bundled WinSCP binaries here | +| PGP / OpenPGP | Excluded | Dedicated encryption module; backend design is separate from string AES-GCM | +| AzureAD session helper | Historical source in Legacy/ | Explicit Graph or Entra authentication for directory operations | +| Azure resource sessions | No wrapper | Az.Accounts owns its Azure context; it is distinct from Graph | +| Exchange Server remoting | Legacy wrappers removed | Use the documented Exchange Server management workflow for the target environment | +| Exchange Online | No wrapper | ExchangeOnlineManagement owns authentication and disconnection | +| Global certificate bypass | Removed | Configure certificate trust for the endpoint instead | + +## Why the old session helpers are excluded + +Close-AzureSession calls Test-AzureSession, which is absent from this repository, +and Disconnect-AzureAD. Microsoft documents migration from the deprecated AzureAD +module to Graph PowerShell; this is more than a command-name substitution. +Graph/Entra directory authentication and Az resource contexts are distinct. The old command must not be +aliased to a function that clears a different service's credentials or context. + +New-ExchangeSession and Close-ExchangeSession have been removed from the repository's +current tree. Their historical implementations targeted on-premises remoting and +had credential handling, session-selection and error-reporting defects. Git history +retains the source if needed; they are not part of the supported v3 API. + +Use Connect-ExchangeOnline and Disconnect-ExchangeOnline directly from Microsoft's +ExchangeOnlineManagement module for Exchange Online. IT-ToolBox does not wrap these +commands, install that module or manage its sessions. Exchange Server on-premises +has a separate management workflow; ExchangeOnlineManagement is not a general +replacement for its remote PowerShell endpoint. + +The removed Enable-SelfSignedCertificate snippet registered a type whose Ignore +method could accept every server certificate through ServicePointManager. The +filename suggested endpoint-specific self-signed certificate support, but the +implementation was a global validation bypass. No replacement bypass is provided. +The old source remains in Git history. + +## Requirements for future session wrappers + +- Choose the service and supported client/server environments explicitly. +- Keep dependencies optional until the operation is invoked; never install them during import. +- Accept service-appropriate authentication without introducing plaintext-password parameters. +- Track the session/context created by the helper; clean up only resources it owns. +- Use explicit disconnect operations and terminating errors; preserve caller preferences. +- Test authentication forwarding, multiple contexts, partial failures and cleanup. +- Verify against a real endpoint before claiming that integration is supported. + +These are implementation requirements for future additions, not new exported APIs. + +## Official references + +- [AzureAD to Microsoft Graph migration](https://learn.microsoft.com/en-us/powershell/microsoftgraph/migration-steps) +- [Microsoft Entra PowerShell overview](https://learn.microsoft.com/en-us/powershell/entra-powershell/overview) +- [Az.Accounts context management](https://learn.microsoft.com/en-us/powershell/azure/context-persistence) +- [Connect-ExchangeOnline](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/connect-exchangeonline) +- [On-premises Exchange remote PowerShell](https://learn.microsoft.com/en-us/powershell/exchange/connect-to-exchange-servers-using-remote-powershell)