From 464db59b38ef724520342478639421a1c78d0161 Mon Sep 17 00:00:00 2001 From: Initzero Date: Mon, 5 Oct 2026 09:21:23 +0200 Subject: [PATCH 1/2] Clarify integration boundaries and retire certificate bypass --- CHANGELOG.md | 10 +++ {Staging => Legacy}/Close-AzureSession.ps1 | 4 ++ Legacy/README.md | 17 +++-- README.md | 22 +++++-- Staging/Enable-SelfSignedCertificate.ps1 | 31 --------- Staging/README.md | 15 ++++- Staging/v3/README.md | 2 +- Tests/IntegrationBoundary.Tests.ps1 | 73 ++++++++++++++++++++++ docs/Integrations.md | 70 +++++++++++++++++++++ 9 files changed, 199 insertions(+), 45 deletions(-) rename {Staging => Legacy}/Close-AzureSession.ps1 (90%) delete mode 100644 Staging/Enable-SelfSignedCertificate.ps1 create mode 100644 Tests/IntegrationBoundary.Tests.ps1 create mode 100644 docs/Integrations.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 760f0d8..bdfe239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 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. +- Document Exchange credential/session-selection defects and separate service lifecycles. +- 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/README.md b/Legacy/README.md index 6f85d05..62f61c5 100644 --- a/Legacy/README.md +++ b/Legacy/README.md @@ -1,8 +1,15 @@ # 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 five 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 | +| New-ExchangeSession | Unreviewed on-premises remoting, credential and authentication behavior | +| Close-ExchangeSession | Known session-selection and error-reporting defects | +| 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. diff --git a/README.md b/README.md index fe04b95..79f8c22 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, Exchange 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,15 @@ 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 remaining AzureAD and Exchange helpers are historical source, not supported +session-management commands. 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..047d8b0 --- /dev/null +++ b/docs/Integrations.md @@ -0,0 +1,70 @@ +# 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 | Historical source in Legacy/ | Separate review against a specific server and client 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 targets an on-premises Microsoft.Exchange remoting endpoint; +it is not an Exchange Online connection helper. Its legacy implementation prompts +in the default parameter set regardless of AskCredentials, can prompt again or add +Credential twice, and only forwards Authentication when explicitly bound despite +its documented Kerberos default. It accepts plaintext passwords. Force and UseHttp +branch on parameter presence rather than the switch's boolean value. + +Close-ExchangeSession uses an undefined sessionObject in its all-sessions branch, +breaks after the first iteration, and does not remove default on-premises sessions +in that branch. ID selection bypasses Online filtering, the name parameter set +cannot bind Online, and broad catches report removal failures as missing sessions. +These implementations are preserved for reference, not advertised as operational. + +Exchange Online uses Connect-ExchangeOnline and Disconnect-ExchangeOnline from +ExchangeOnlineManagement. Its modern connections cannot be identified or cleaned +up reliably by matching Get-PSSession computer names. On-premises remoting has a +different lifecycle and platform/authentication requirements. + +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) From 959b939a54f6409aca93619cb2e13f4c62b12f4c Mon Sep 17 00:00:00 2001 From: Initzero Date: Mon, 5 Oct 2026 09:27:50 +0200 Subject: [PATCH 2/2] Remove legacy Exchange wrappers and clarify integration boundaries --- CHANGELOG.md | 3 +- Legacy/Close-ExchangeSession.ps1 | 221 -------------------------- Legacy/New-ExchangeSession.ps1 | 259 ------------------------------- Legacy/README.md | 7 +- README.md | 7 +- docs/Integrations.md | 27 ++-- 6 files changed, 20 insertions(+), 504 deletions(-) delete mode 100644 Legacy/Close-ExchangeSession.ps1 delete mode 100644 Legacy/New-ExchangeSession.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index bdfe239..5bd3fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ - Archive Close-AzureSession in Legacy; document its missing Test-AzureSession dependency. - Remove the process-wide certificate-validation bypass snippet; retain Git history. -- Document Exchange credential/session-selection defects and separate service lifecycles. +- 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. 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 62f61c5..099bab3 100644 --- a/Legacy/README.md +++ b/Legacy/README.md @@ -1,15 +1,16 @@ # Historical commands -These five commands are reference implementations, not a supported compatibility +These three commands are reference implementations, not a supported compatibility layer. They are never loaded or exported by v3. | 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 | -| New-ExchangeSession | Unreviewed on-premises remoting, credential and authentication behavior | -| Close-ExchangeSession | Known session-selection and error-reporting defects | | 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 79f8c22..8a6682d 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ 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 historical string encryption, Exchange and AzureAD 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; historical service integrations remain excluded. - Only the twenty-nine listed commands are exported. Private helpers, variables and aliases @@ -405,8 +405,9 @@ MyInvocation or silently return a module filename. 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 remaining AzureAD and Exchange helpers are historical source, not supported -session-management commands. The process-wide certificate-bypass snippet has been +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, diff --git a/docs/Integrations.md b/docs/Integrations.md index 047d8b0..9505338 100644 --- a/docs/Integrations.md +++ b/docs/Integrations.md @@ -13,7 +13,7 @@ their documented optional dependencies when explicitly invoked. | 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 | Historical source in Legacy/ | Separate review against a specific server and client environment | +| 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 | @@ -25,23 +25,16 @@ 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 targets an on-premises Microsoft.Exchange remoting endpoint; -it is not an Exchange Online connection helper. Its legacy implementation prompts -in the default parameter set regardless of AskCredentials, can prompt again or add -Credential twice, and only forwards Authentication when explicitly bound despite -its documented Kerberos default. It accepts plaintext passwords. Force and UseHttp -branch on parameter presence rather than the switch's boolean value. +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. -Close-ExchangeSession uses an undefined sessionObject in its all-sessions branch, -breaks after the first iteration, and does not remove default on-premises sessions -in that branch. ID selection bypasses Online filtering, the name parameter set -cannot bind Online, and broad catches report removal failures as missing sessions. -These implementations are preserved for reference, not advertised as operational. - -Exchange Online uses Connect-ExchangeOnline and Disconnect-ExchangeOnline from -ExchangeOnlineManagement. Its modern connections cannot be identified or cleaned -up reliably by matching Get-PSSession computer names. On-premises remoting has a -different lifecycle and platform/authentication requirements. +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