diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ca52a..aa50a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -## [1.0.0] - 2026-02-03 +## [2.1.0] - 2026-06-17 + +### Added + +- **WMI dependency service enforcement** — `Confirm-CWAADependencyService` ensures `winmgmt` (WMI) is set to Automatic startup and Running before install, repair, and service start. Configurable via `$Script:CWAADependencyServiceNames`; respects `-WhatIf`. Event IDs 2030–2039. + +## [2.0.0] - 2026-02-03 ### Added diff --git a/GitVersion.yml b/GitVersion.yml index 5f05d68..8d57e14 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -28,9 +28,11 @@ branches: tag: PR increment: Inherit -major-version-bump-message: '(breaking\schange|breaking|major)\b' -minor-version-bump-message: '(adds?|features?|minor)\b' -patch-version-bump-message: '\s?(fix|patch)' +# Version bumps require explicit +semver: prefix in commit messages +# e.g. "+semver: major", "+semver: minor", "+semver: patch" +major-version-bump-message: '\+semver:\s?major' +minor-version-bump-message: '\+semver:\s?minor' +patch-version-bump-message: '\+semver:\s?patch' no-bump-message: '\+semver:\s?(none|skip)' ignore: diff --git a/Tests/ConnectWiseAutomateAgent.Mocked.CrossCutting.Tests.ps1 b/Tests/ConnectWiseAutomateAgent.Mocked.CrossCutting.Tests.ps1 index 4183e88..59cb404 100644 --- a/Tests/ConnectWiseAutomateAgent.Mocked.CrossCutting.Tests.ps1 +++ b/Tests/ConnectWiseAutomateAgent.Mocked.CrossCutting.Tests.ps1 @@ -95,6 +95,7 @@ Describe 'Pipeline Support' { } Mock Write-CWAAEventLog {} Mock Get-CimInstance { return @() } + Mock Confirm-CWAADependencyService {} # Pipe an object with Server and LocationID — bind via ValueFromPipelineByPropertyName # InstallerToken is provided explicitly (it wouldn't come from Get-CWAAInfo output) @@ -179,8 +180,8 @@ Describe 'Pipeline Support' { It 'accepts Server and TrayPort from piped PSCustomObject' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CWAAInfo { [PSCustomObject]@{ TrayPort = '42000' } } - Mock Invoke-Expression { return $null } - function netstat { return @() } + # No process is using the TrayPort: netstat returns nothing matching it. + Mock Get-CWAANetstat { @() } [PSCustomObject]@{ Server = 'automate.example.com'; TrayPort = 42000 } | Test-CWAAPort -Quiet } @@ -223,7 +224,13 @@ Describe 'Pipeline Support' { It 'Register-CWAAHealthCheckTask accepts Server as string[] and builds valid command' { $result = InModuleScope 'ConnectWiseAutomateAgent' { - Mock schtasks { return $null } + # The mock must set $LASTEXITCODE for the /CREATE branch; the code checks it + # after the native call, and a bare 'return $null' leaks a prior exit code. + Mock schtasks { + if ($args -contains '/QUERY') { throw 'Task not found' } + elseif ($args -contains '/DELETE') { return $null } + elseif ($args -contains '/CREATE') { $global:LASTEXITCODE = 0; return 'SUCCESS' } + } Mock New-CWAABackup {} [PSCustomObject]@{ diff --git a/Tests/ConnectWiseAutomateAgent.Mocked.Installation.Tests.ps1 b/Tests/ConnectWiseAutomateAgent.Mocked.Installation.Tests.ps1 index 29de007..1069ae9 100644 --- a/Tests/ConnectWiseAutomateAgent.Mocked.Installation.Tests.ps1 +++ b/Tests/ConnectWiseAutomateAgent.Mocked.Installation.Tests.ps1 @@ -205,6 +205,7 @@ Describe 'Repair-CWAA' { It 'returns ActionTaken=None with success' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } Mock Get-CWAAInfo { @@ -230,6 +231,7 @@ Describe 'Repair-CWAA' { $script:callCount = 0 $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } # First call returns old LastSuccessStatus, subsequent calls return recent @@ -268,6 +270,7 @@ Describe 'Repair-CWAA' { It 'triggers reinstall after failed restart' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } # Return old date consistently. The wait loop calls Get-CWAAInfo @@ -308,6 +311,7 @@ Describe 'Repair-CWAA' { It 'attempts a fresh install with provided parameters' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { return $null } Mock Redo-CWAA {} @@ -323,6 +327,7 @@ Describe 'Repair-CWAA' { It 'reports error when no install settings are available' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { return $null } Mock Get-CWAAInfo { throw 'Not installed' } @@ -340,6 +345,7 @@ Describe 'Repair-CWAA' { It 'returns error about unreachable server' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } Mock Get-CWAAInfo { @@ -364,6 +370,7 @@ Describe 'Repair-CWAA' { It 'reinstalls with the correct server' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} Mock Stop-Process {} Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } Mock Get-CWAAInfo { @@ -383,6 +390,30 @@ Describe 'Repair-CWAA' { $result.Message | Should -Match 'correct server' } } + + Context 'WMI dependency service' { + It 'ensures the dependency service before remediation' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-CimInstance { @() } + Mock Confirm-CWAADependencyService {} + Mock Stop-Process {} + Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } + Mock Get-CWAAInfo { + [PSCustomObject]@{ + Server = @('automate.example.com') + LastSuccessStatus = (Get-Date).AddMinutes(-30).ToString() + HeartbeatLastSent = (Get-Date).AddMinutes(-15).ToString() + HeartbeatLastReceived = (Get-Date).AddMinutes(-15).ToString() + } + } + Mock Write-CWAAEventLog {} + + Repair-CWAA -InstallerToken 'abc123' -Confirm:$false + + Should -Invoke Confirm-CWAADependencyService -Scope It -Times 1 + } + } + } } # ----------------------------------------------------------------------------- @@ -795,11 +826,8 @@ Describe 'Test-CWAAPort' { It 'returns $true' { $result = InModuleScope 'ConnectWiseAutomateAgent' { Mock Get-CWAAInfo { [PSCustomObject]@{ TrayPort = '42000' } } - # netstat returns no matching output for the port - $env_windir = $env:windir - Mock Invoke-Expression { return $null } - # Mock netstat by ensuring no process is found on the port - function netstat { return @() } + # No process is using the TrayPort: netstat returns nothing matching it. + Mock Get-CWAANetstat { @() } Test-CWAAPort -TrayPort 42000 -Quiet } $result | Should -BeTrue @@ -813,8 +841,8 @@ Describe 'Test-CWAAPort' { Mock Get-CWAAInfoBackup { return $null } Mock Get-Process { [PSCustomObject]@{ ProcessName = 'LTSvc'; Id = 1234 } } Mock Test-Connection { return $true } - # Mock netstat to return a line matching the port with a PID - $Script:MockNetstatOutput = " TCP 0.0.0.0:42000 0.0.0.0:0 LISTENING 1234" + # netstat reports a process (PID 1234) listening on the TrayPort. + Mock Get-CWAANetstat { ' TCP 0.0.0.0:42000 0.0.0.0:0 LISTENING 1234' } # We need to test the output message Test-CWAAPort -TrayPort 42000 -Server 'automate.example.com' 2>&1 diff --git a/Tests/ConnectWiseAutomateAgent.Mocked.PrivateHelpers.Tests.ps1 b/Tests/ConnectWiseAutomateAgent.Mocked.PrivateHelpers.Tests.ps1 index c2e350d..d539df0 100644 --- a/Tests/ConnectWiseAutomateAgent.Mocked.PrivateHelpers.Tests.ps1 +++ b/Tests/ConnectWiseAutomateAgent.Mocked.PrivateHelpers.Tests.ps1 @@ -614,3 +614,106 @@ Describe 'Invoke-CWAAMsiInstaller' { } } } + +# ----------------------------------------------------------------------------- +# Confirm-CWAADependencyService Tests +# ----------------------------------------------------------------------------- + +Describe 'Confirm-CWAADependencyService' { + + It 'sets startup type to Automatic for the dependency service' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { [PSCustomObject]@{ Name = 'winmgmt'; Status = 'Running' } } + Mock Set-Service {} + Mock Start-Service {} + Mock Wait-CWAACondition { $true } + + Confirm-CWAADependencyService -Confirm:$false + + Should -Invoke Set-Service -Scope It -ParameterFilter { $StartupType -eq 'Automatic' } + } + } + + It 'starts the service when it is not running' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { [PSCustomObject]@{ Name = 'winmgmt'; Status = 'Stopped' } } + Mock Set-Service {} + Mock Start-Service {} + Mock Wait-CWAACondition { $true } + Mock Write-CWAAEventLog {} + + Confirm-CWAADependencyService -Confirm:$false + + Should -Invoke Start-Service -Scope It -Times 1 + Should -Invoke Write-CWAAEventLog -Scope It -ParameterFilter { $EventId -eq 2030 } + } + } + + It 'does not start the service when already running' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { [PSCustomObject]@{ Name = 'winmgmt'; Status = 'Running' } } + Mock Set-Service {} + Mock Start-Service {} + Mock Wait-CWAACondition { $true } + + Confirm-CWAADependencyService -Confirm:$false + + Should -Invoke Start-Service -Scope It -Times 0 + } + } + + It 'falls back to sc.exe when Start-Service throws' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { [PSCustomObject]@{ Name = 'winmgmt'; Status = 'Stopped' } } + Mock Set-Service {} + Mock Start-Service { throw 'cannot start' } + Mock Wait-CWAACondition { $true } + Mock Write-CWAAEventLog {} + + { Confirm-CWAADependencyService -Confirm:$false } | Should -Not -Throw + # No assertion on sc.exe (native exe is not easily mockable); the test confirms + # the throw is caught and the function completes without error. + } + } + + It 'logs a warning event when the service never reaches Running' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { [PSCustomObject]@{ Name = 'winmgmt'; Status = 'Stopped' } } + Mock Set-Service {} + Mock Start-Service {} + Mock Wait-CWAACondition { $false } + Mock Write-CWAAEventLog {} + + Confirm-CWAADependencyService -Confirm:$false -WarningAction SilentlyContinue + + Should -Invoke Write-CWAAEventLog -Scope It -ParameterFilter { $EventId -eq 2031 } + } + } + + It 'skips a service that does not exist without error' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { $null } + Mock Set-Service {} + Mock Start-Service {} + + { Confirm-CWAADependencyService -Confirm:$false } | Should -Not -Throw + + Should -Invoke Set-Service -Scope It -Times 0 + Should -Invoke Start-Service -Scope It -Times 0 + } + } + + It 'makes no changes under -WhatIf' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-Service { [PSCustomObject]@{ Name = 'winmgmt'; Status = 'Stopped' } } + Mock Set-Service {} + Mock Start-Service {} + Mock Wait-CWAACondition { $true } + + Confirm-CWAADependencyService -WhatIf + + Should -Invoke Set-Service -Scope It -Times 0 + Should -Invoke Start-Service -Scope It -Times 0 + } + } +} diff --git a/Tests/ConnectWiseAutomateAgent.Mocked.ServiceOps.Tests.ps1 b/Tests/ConnectWiseAutomateAgent.Mocked.ServiceOps.Tests.ps1 index 3227e04..6e37853 100644 --- a/Tests/ConnectWiseAutomateAgent.Mocked.ServiceOps.Tests.ps1 +++ b/Tests/ConnectWiseAutomateAgent.Mocked.ServiceOps.Tests.ps1 @@ -268,6 +268,20 @@ Describe 'Start-CWAA' { Should -Invoke Invoke-CWAACommand -Times 1 -Scope It -ParameterFilter { $Command -eq 'Send Status' } } } + + It 'ensures the WMI dependency service before starting the agent' { + InModuleScope 'ConnectWiseAutomateAgent' { + Mock Get-CWAAInfo { [PSCustomObject]@{ TrayPort = '42000' } } + Mock Get-Service { [PSCustomObject]@{ Name = 'LTService'; Status = 'Running' } } + Mock Set-Service {} + Mock Invoke-CWAACommand {} + Mock Confirm-CWAADependencyService {} + + Start-CWAA -Confirm:$false + + Should -Invoke Confirm-CWAADependencyService -Times 1 -Scope It + } + } } # ============================================================================= diff --git a/source/Private/Confirm-CWAADependencyService.ps1 b/source/Private/Confirm-CWAADependencyService.ps1 new file mode 100644 index 0000000..e675a5c --- /dev/null +++ b/source/Private/Confirm-CWAADependencyService.ps1 @@ -0,0 +1,102 @@ +function Confirm-CWAADependencyService { + <# + .SYNOPSIS + Ensures the OS services the Automate agent depends on are Automatic and Running. + .DESCRIPTION + Iterates the services named in $Script:CWAADependencyServiceNames (winmgmt/WMI by + default) and makes sure each is set to Automatic startup and is Running. The agent + relies on WMI for inventory, scripting, and check-in, and this module's own + Get-CimInstance calls fail silently when winmgmt is disabled, so this runs before + install, repair, and service start. + + For each dependency service: + - If the service is not present, it is skipped (logged via Write-Debug). + - Startup type is set to Automatic unconditionally. This is done without reading + the current StartMode (which would require Win32_Service via WMI, the very + service that may be down) and is idempotent. + - If the service is not Running, a start is attempted via Start-Service (which + resolves dependencies such as RpcSs), falling back to sc.exe start. The function + then polls up to $Script:CWAAServiceWaitTimeoutSec for the Running state. + + Remediation outcomes are written to the Windows Event Log (EventIds 2030-2032). + Honors -WhatIf via ShouldProcess; under -WhatIf no changes are made. + .PARAMETER ServiceName + One or more service names to ensure. Defaults to $Script:CWAADependencyServiceNames. + Exposed primarily for testing. + .NOTES + Version: 1.0.0 + Author: Chris Taylor + Private function - not exported. + .LINK + https://github.com/christaylorcodes/ConnectWiseAutomateAgent + #> + [CmdletBinding(SupportsShouldProcess = $True)] + Param( + [Parameter()] + [string[]]$ServiceName = $Script:CWAADependencyServiceNames + ) + + Begin { + Write-Debug "Starting $($MyInvocation.InvocationName)" + } + + Process { + foreach ($name in $ServiceName) { + $service = Get-Service $name -ErrorAction SilentlyContinue + if (-not $service) { + Write-Debug "Dependency service '$name' not found. Skipping." + continue + } + + Try { + # Force Automatic startup. Set unconditionally; reading the current start mode + # reliably needs WMI, which may be the disabled service we are repairing. + if ($PSCmdlet.ShouldProcess($name, 'Set service startup type to Automatic')) { + Set-Service $name -StartupType Automatic -EA 0 -Confirm:$False -WhatIf:$False + } + + # Start the service if it is not already running. + $service = Get-Service $name -ErrorAction SilentlyContinue + if ($service -and $service.Status -ne 'Running') { + if ($PSCmdlet.ShouldProcess($name, 'Start service')) { + Write-Verbose "Dependency service '$name' is $($service.Status). Starting." + Try { + Start-Service $name -ErrorAction Stop -WhatIf:$False -Confirm:$False + } + Catch { + # Fall back to sc.exe, consistent with the rest of the module. + Write-Debug "Start-Service failed for '$name' ($($_.Exception.Message)). Falling back to sc.exe." + $Null = & "$env:windir\system32\sc.exe" start "$name" 2>'' + } + + # GetNewClosure bakes the loop-local $name into the scriptblock so it + # resolves correctly when Wait-CWAACondition invokes it from its own scope. + $running = Wait-CWAACondition -Condition { + (Get-Service $name -EA 0 | Select-Object -Expand Status -EA 0) -eq 'Running' + }.GetNewClosure() -TimeoutSeconds $Script:CWAAServiceWaitTimeoutSec -IntervalSeconds 2 -Activity "Dependency service '$name' starting" + + if ($running) { + Write-Verbose "Dependency service '$name' is Running." + Write-CWAAEventLog -EventId 2030 -EntryType Information -Message "Dependency service '$name' set to Automatic and started." + } + else { + Write-Warning "Dependency service '$name' did not reach the Running state." + Write-CWAAEventLog -EventId 2031 -EntryType Warning -Message "Dependency service '$name' was set to Automatic but did not reach the Running state." + } + } + } + else { + Write-Debug "Dependency service '$name' is already Running." + } + } + Catch { + Write-Warning "Failed to ensure dependency service '$name'. $($_.Exception.Message)" + Write-CWAAEventLog -EventId 2032 -EntryType Error -Message "Failed to ensure dependency service '$name'. Error: $($_.Exception.Message)" + } + } + } + + End { + Write-Debug "Exiting $($MyInvocation.InvocationName)" + } +} diff --git a/source/Private/Get-CWAANetstat.ps1 b/source/Private/Get-CWAANetstat.ps1 new file mode 100644 index 0000000..e7ebd6d --- /dev/null +++ b/source/Private/Get-CWAANetstat.ps1 @@ -0,0 +1,24 @@ +function Get-CWAANetstat { + <# + .SYNOPSIS + Returns raw output from the local computer's netstat.exe. + .DESCRIPTION + Thin wrapper around the system netstat.exe (invoked with -a -o -n) that exists to + provide a single, mockable seam for the external process call. Callers such as + Test-CWAAPort pipe the output to Select-String to locate processes using a port. + + The full system path ($env:windir\system32\netstat.exe) is used deliberately to + avoid PATH hijacking, which is why this cannot be intercepted by mocking a bare + 'netstat' command - tests mock this function instead. + .NOTES + Version: 1.0.0 + Author: Chris Taylor + Private function - not exported. + .LINK + https://github.com/christaylorcodes/ConnectWiseAutomateAgent + #> + [CmdletBinding()] + Param() + + & "$env:windir\system32\netstat.exe" -a -o -n +} diff --git a/source/Private/Initialize/Initialize-CWAA.ps1 b/source/Private/Initialize/Initialize-CWAA.ps1 index a9a17f9..a9e9031 100644 --- a/source/Private/Initialize/Initialize-CWAA.ps1 +++ b/source/Private/Initialize/Initialize-CWAA.ps1 @@ -155,6 +155,12 @@ function Initialize-CWAA { # All service names including LabVNC — for full service cleanup in Uninstall-CWAA. $Script:CWAAAllServiceNames = @('LTService', 'LTSvcMon', 'LabVNC') + # OS services the agent depends on — ensured Automatic + Running before install/repair/start + # via Confirm-CWAADependencyService. The agent relies on WMI (winmgmt) for inventory, + # scripting, and check-in; a disabled winmgmt breaks the agent (and this module's own + # Get-CimInstance calls). Kept as a list so additional dependencies are trivial to add. + $Script:CWAADependencyServiceNames = @('winmgmt') + # Service credential storage -- populated on-demand by Get-CWAAProxy $Script:LTServiceKeys = [PSCustomObject]@{ ServerPasswordString = '' diff --git a/source/Private/Write-CWAAEventLog.ps1 b/source/Private/Write-CWAAEventLog.ps1 index b6b6827..1af1f69 100644 --- a/source/Private/Write-CWAAEventLog.ps1 +++ b/source/Private/Write-CWAAEventLog.ps1 @@ -14,6 +14,7 @@ function Write-CWAAEventLog { Event ID ranges by category: 1000-1039 Installation (Install, Uninstall, Redo, Update) 2000-2029 Service Control (Start, Stop, Restart) + 2030-2039 Dependency Services (winmgmt/WMI auto-start + start) 3000-3069 Configuration (Reset, Backup, Proxy, LogLevel, AddRemove) 4000-4039 Health/Monitoring (Repair, Register/Unregister task) .NOTES diff --git a/source/Public/InstallUninstall/Install-CWAA.ps1 b/source/Public/InstallUninstall/Install-CWAA.ps1 index d3cee53..d61cc71 100644 --- a/source/Public/InstallUninstall/Install-CWAA.ps1 +++ b/source/Public/InstallUninstall/Install-CWAA.ps1 @@ -144,6 +144,9 @@ function Install-CWAA { Throw 'Needs to be ran as Administrator' } + # Ensure WMI (winmgmt) is Automatic + Running before the MSI runs and the agent registers. + Confirm-CWAADependencyService + $Null = Test-CWAADotNetPrerequisite -SkipDotNet:$SkipDotNet -Force:$Force $InstallBase = $Script:CWAAInstallerTempPath diff --git a/source/Public/Service/Repair-CWAA.ps1 b/source/Public/Service/Repair-CWAA.ps1 index 1ac2158..7e05014 100644 --- a/source/Public/Service/Repair-CWAA.ps1 +++ b/source/Public/Service/Repair-CWAA.ps1 @@ -79,6 +79,10 @@ Begin { Write-Debug "Starting $($MyInvocation.InvocationName)" + # Ensure WMI (winmgmt) is Automatic + Running first. The agent depends on it, and the + # Get-CimInstance call below silently no-ops on a WMI-disabled box, so this must run before it. + Confirm-CWAADependencyService + # Kill duplicate Repair-CWAA processes to prevent overlapping remediation # Uses CIM for reliable command-line matching (Get-Process cannot filter by arguments) if ($PSCmdlet.ShouldProcess('Duplicate Repair-CWAA processes', 'Terminate')) { diff --git a/source/Public/Service/Start-CWAA.ps1 b/source/Public/Service/Start-CWAA.ps1 index a183f0d..e1b96bf 100644 --- a/source/Public/Service/Start-CWAA.ps1 +++ b/source/Public/Service/Start-CWAA.ps1 @@ -38,6 +38,9 @@ function Start-CWAA { Process { if (-not (Test-CWAAServiceExists -WriteErrorOnMissing)) { return } + # Ensure WMI (winmgmt) is Automatic + Running before starting the agent. The agent + # depends on it for check-in, inventory, and scripting. + Confirm-CWAADependencyService Try { if ((('LTService') | Get-Service -EA 0 | Where-Object { $_.Status -eq 'Stopped' } | Measure-Object | Select-Object -Expand Count) -gt 0) { Try { $netstat = & "$env:windir\system32\netstat.exe" -a -o -n 2>'' | Select-String -Pattern " .*[0-9\.]+:$($Port).*[0-9\.]+:[0-9]+ .*?([0-9]+)" -EA 0 } diff --git a/source/Public/Test-CWAAPort.ps1 b/source/Public/Test-CWAAPort.ps1 index d211102..0f16c12 100644 --- a/source/Public/Test-CWAAPort.ps1 +++ b/source/Public/Test-CWAAPort.ps1 @@ -101,7 +101,7 @@ function Test-CWAAPort { [array]$processes = @() # Get all processes using the TrayPort (default 42000) Try { - $netstatOutput = & "$env:windir\system32\netstat.exe" -a -o -n | Select-String -Pattern " .*[0-9\.]+:$($TrayPort).*[0-9\.]+:[0-9]+ .*?([0-9]+)" -EA 0 + $netstatOutput = Get-CWAANetstat | Select-String -Pattern " .*[0-9\.]+:$($TrayPort).*[0-9\.]+:[0-9]+ .*?([0-9]+)" -EA 0 } Catch { Write-Output 'Error calling netstat.exe.'