sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #1
I am looking for a way to programmatically Uncheck Enable Selective Suspend on all USB Serial Ports in device manager. I changed the value of DeviceSelectiveSuspend to 0 in the registry manager but the change doesn't persist over a reboot. I have also changed my power settings to disable USB selective suspend. I cannot get this change to persist any way other than opening device manager going to advanced settings and actually unchecking Enable Selective Suspend in each COM port device. I have a large number of devices and PCs I need to do this for and do not want to manually uncheck this box on every device on every PC. Happy to share photos of everything I have tried in the registry editor that has not worked yet. This is specifically for FTDI devices if that help. Windows 11 Desktop PC.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #21
What is the currentcontrolset enum property for unchecking serial enumerator in device manager on a com port
- Joined
- Mar 14, 2023
- Messages
- 97,513
Short answer: there isn’t a single built‑in “CurrentControlSet\Enum” value name that always corresponds to the Serial Enumerator checkbox — it’s a per‑device/driver property stored under that device’s hardware key (HKLM\SYSTEM\CurrentControlSet\Enum\<device‑instance>\Device Parameters) and the exact value name is driver‑specific. For selective‑suspend the driver uses values like DeviceIdleEnabled / DefaultIdleState / UserSetDeviceIdleEnabled / SSIdleTimeout, but “Serial Enumerator” is a separate advanced property and may be a different registry value (or not exposed at all) depending on the driver.
How you find (and then set) the exact registry property for “Serial Enumerator”
1) Run PowerShell elevated and get the device InstanceId (replace the filter if needed):
Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like 'FTDI' } | Format-List FriendlyName, InstanceId, Status
2) Inspect the device’s Device Parameters (before toggling the GUI). Replace the $inst value with one InstanceId returned above:
$inst = 'PASTE_THE_InstanceId_HERE'
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$regPath = Join-Path $enumBase ($inst -replace '\','\')
$dp = Join-Path $regPath 'Device Parameters'
Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue | Format-List * | Out-File "$env:USERPROFILE\Desktop\FTDI_before.txt"
3) Manually toggle the Serial Enumerator checkbox once in Device Manager → Ports → <COMx> → Properties → Advanced → Serial Enumerator (uncheck or check) and click OK.
4) Capture the after state:
Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue | Format-List * | Out-File "$env:USERPROFILE\Desktop\FTDI_after.txt"
5) Diff the files (or open both) and look for the property name that changed. That property is what Device Manager is changing and is what you can script. If you want an automatic JSON diff instead of manual compare:
$before = Get-ItemProperty -Path $dp | ConvertTo-Json -Depth 3
$value = <Value> # e.g. 0 to uncheck / disable
Get-PnpDevice -Class Ports | Where-Object { $.Manufacturer -like 'FTDI' -and $.InstanceId } |
ForEach-Object {
$inst = $.InstanceId
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst\Device Parameters"
if (-not (Test-Path $regPath) { New-Item -Path $regPath -Force | Out-Null }
try {
New-ItemProperty -Path $regPath -Name $prop -Value $value -PropertyType DWord -Force -ErrorAction Stop
Write-Output "Updated $inst\$prop"
} catch {
Write-Warning "Failed to write $prop at $regPath : $($.Exception.Message)"
}
}
Why editing the INF is the robust/persistent option
HKR,,DeviceIdleEnabled,0x00010001,0
HKR,,DefaultIdleState,0x00010001,0
HKR,,UserSetDeviceIdleEnabled,0x00010001,0
HKR,,SSIdleTimeout,0x00010001,0
; then in the device Install section:
AddReg = MyFTDI.AddReg
(You can target the exact property name you discovered instead of the example names above. See also where Windows stores the published INF and the original package (oem*.inf in C:\Windows\INF and original in DriverStore) before editing/deploying a modified INF.
Warnings & practical notes
How you find (and then set) the exact registry property for “Serial Enumerator”
1) Run PowerShell elevated and get the device InstanceId (replace the filter if needed):
Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like 'FTDI' } | Format-List FriendlyName, InstanceId, Status
2) Inspect the device’s Device Parameters (before toggling the GUI). Replace the $inst value with one InstanceId returned above:
$inst = 'PASTE_THE_InstanceId_HERE'
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$regPath = Join-Path $enumBase ($inst -replace '\','\')
$dp = Join-Path $regPath 'Device Parameters'
Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue | Format-List * | Out-File "$env:USERPROFILE\Desktop\FTDI_before.txt"
3) Manually toggle the Serial Enumerator checkbox once in Device Manager → Ports → <COMx> → Properties → Advanced → Serial Enumerator (uncheck or check) and click OK.
4) Capture the after state:
Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue | Format-List * | Out-File "$env:USERPROFILE\Desktop\FTDI_after.txt"
5) Diff the files (or open both) and look for the property name that changed. That property is what Device Manager is changing and is what you can script. If you want an automatic JSON diff instead of manual compare:
$before = Get-ItemProperty -Path $dp | ConvertTo-Json -Depth 3
toggle GUI now
$after = Get-ItemProperty -Path $dp | ConvertTo-Json -Depth 3Save or compare the JSON strings in your editor — easiest to inspect the two files.
Once you identify the property name (example names you might see: DeviceIdleEnabled, UserSetDeviceIdleEnabled, SSIdleTimeout — but “SerialEnumerator” could be a different name or vendor name), you can set it for all FTDI COM ports with an elevated script like this (replace <PropertyName> and <Value> with the real name/value you discovered):Run as Administrator
$prop = '<PropertyName>' # e.g. 'SerialEnumerator' or whatever your diff showed$value = <Value> # e.g. 0 to uncheck / disable
Get-PnpDevice -Class Ports | Where-Object { $.Manufacturer -like 'FTDI' -and $.InstanceId } |
ForEach-Object {
$inst = $.InstanceId
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst\Device Parameters"
if (-not (Test-Path $regPath) { New-Item -Path $regPath -Force | Out-Null }
try {
New-ItemProperty -Path $regPath -Name $prop -Value $value -PropertyType DWord -Force -ErrorAction Stop
Write-Output "Updated $inst\$prop"
} catch {
Write-Warning "Failed to write $prop at $regPath : $($.Exception.Message)"
}
}
then force device rescan
pnputil /scan-devicesWhy editing the INF is the robust/persistent option
- If the driver’s installer writes the device defaults at installation, changes you make in the Enum tree can be overwritten on re‑enumeration or driver updates. The supported persistent fix is to add an AddReg section to the driver INF so Windows writes the desired default into HKLM\SYSTEM\CurrentControlSet\Enum\<instance>\Device Parameters when the device is installed. Example AddReg lines (illustrative — adapt for the exact InstallSection in your INF):
HKR,,DeviceIdleEnabled,0x00010001,0
HKR,,DefaultIdleState,0x00010001,0
HKR,,UserSetDeviceIdleEnabled,0x00010001,0
HKR,,SSIdleTimeout,0x00010001,0
; then in the device Install section:
AddReg = MyFTDI.AddReg
(You can target the exact property name you discovered instead of the example names above. See also where Windows stores the published INF and the original package (oem*.inf in C:\Windows\INF and original in DriverStore) before editing/deploying a modified INF.
Warnings & practical notes
- Must run PowerShell elevated to read/write under HKLM\SYSTEM\CurrentControlSet\Enum (Enum keys are protected — some systems may refuse writes unless run as SYSTEM or ACLs are changed). If you get Access Denied, paste the exact error and I’ll give the safest next step.
- Editing INF files affects driver signing. On 64‑bit Windows you’ll need a signed package for production deployments; for testing you can install locally with pnputil.
- The property name for “Serial Enumerator” is vendor/driver specific — use the “before/after” method above to discover the exact registry value on your machines.
- Walk you through running the “before/after” capture and parse the two files you produce (paste them here) and I’ll tell you the exact value name and provide a one‑line command to set it across all FTDI ports; or
- If you already ran the before/after and have the two files, paste the differing lines and I’ll give the exact bulk PowerShell to apply it; or
- Prepare the exact AddReg insertion for oem167.inf (or the INF you told me about) — paste the device InstallSection (or the INF contents) and I’ll return the ready‑to‑paste snippet + pnputil commands.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #23
What is the New-ItemProperty for unchecking serial enumerator in advanced setting on device manager for com port
- Joined
- Mar 14, 2023
- Messages
- 97,513
Short answer: there is no single universal New‑ItemProperty call that will “uncheck Serial Enumerator” for every COM port — that advanced checkbox is a per‑device driver property stored under each device’s hardware key (HKLM\SYSTEM\CurrentControlSet\Enum\<device‑instance>\Device Parameters). You must discover the exact property name your driver uses (then set it). See details and ready‑to‑run PowerShell below.
1) How to discover the exact registry property the Device Manager toggles
2) Example New-ItemProperty command (once you know the property name)
Replace <INSTANCE> and <PropertyName> below with the InstanceId and the property you discovered. Value 0 = unchecked/disabled in the usual cases.
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\<INSTANCE>\Device Parameters" -Name "<PropertyName>" -PropertyType DWord -Value 0 -Force
Example (common selective‑suspend properties — may or may not be the one you need):
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_6001+XXXXXXXX\0000\Device Parameters" -Name "DeviceIdleEnabled" -PropertyType DWord -Value 0 -Force
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_6001+XXXXXXXX\0000\Device Parameters" -Name "DefaultIdleState" -PropertyType DWord -Value 0 -Force
These example names are commonly used for selective‑suspend; the Serial Enumerator checkbox may use a different name — that’s why do the before/after diff first.
3) Bulk script to set the discovered property on all FTDI COM ports
Run PowerShell as Administrator. Replace <PropertyName> and <Value> with the name/value you found (e.g., 'SerialEnumerator', 0):
$prop = '<PropertyName>' # e.g. 'SerialEnumerator' or the value found in the diff
$value = 0
Get-PnpDevice -Class Ports | Where-Object { $.Manufacturer -like 'FTDI' -and $.InstanceId } | ForEach-Object {
$inst = $.InstanceId
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst\Device Parameters"
if (-not (Test-Path $regPath) { New-Item -Path $regPath -Force | Out-Null }
try {
New-ItemProperty -Path $regPath -Name $prop -PropertyType DWord -Value $value -Force -ErrorAction Stop
Write-Output "Updated $inst\$prop"
} catch {
Write-Warning "Failed $inst: $($.Exception.Message)"
}
}
pnputil /scan-devices
Notes:
HKR,,<PropertyName>,0x00010001,0
; then add to the Install section:
AddReg = MyFTDI.AddReg
1) How to discover the exact registry property the Device Manager toggles
- Run PowerShell elevated and get one FTDI COM device InstanceId:
Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like 'FTDI' } | Format-List FriendlyName,InstanceId,Status - Read all values under that device’s Device Parameters (replace the InstanceId):
$inst = 'PASTE_INSTANCEID_HERE'
$dp = "HKLM:\SYSTEM\CurrentControlSet\Enum\$($inst -replace '\','\')\Device Parameters"
Get-ItemProperty -Path $dp | Format-List * > "$env:USERPROFILE\Desktop\FTDI_before.txt" - In Device Manager toggle the Serial Enumerator checkbox once (Advanced → Serial Enumerator) and then run:
Get-ItemProperty -Path $dp | Format-List * > "$env:USERPROFILE\Desktop\FTDI_after.txt" - Diff those two files (or open them) and note which property name changed. That is the property the GUI controls. (If you prefer an automated JSON diff I can give that one‑liner.
2) Example New-ItemProperty command (once you know the property name)
Replace <INSTANCE> and <PropertyName> below with the InstanceId and the property you discovered. Value 0 = unchecked/disabled in the usual cases.
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\<INSTANCE>\Device Parameters" -Name "<PropertyName>" -PropertyType DWord -Value 0 -Force
Example (common selective‑suspend properties — may or may not be the one you need):
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_6001+XXXXXXXX\0000\Device Parameters" -Name "DeviceIdleEnabled" -PropertyType DWord -Value 0 -Force
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_6001+XXXXXXXX\0000\Device Parameters" -Name "DefaultIdleState" -PropertyType DWord -Value 0 -Force
These example names are commonly used for selective‑suspend; the Serial Enumerator checkbox may use a different name — that’s why do the before/after diff first.
3) Bulk script to set the discovered property on all FTDI COM ports
Run PowerShell as Administrator. Replace <PropertyName> and <Value> with the name/value you found (e.g., 'SerialEnumerator', 0):
$prop = '<PropertyName>' # e.g. 'SerialEnumerator' or the value found in the diff
$value = 0
Get-PnpDevice -Class Ports | Where-Object { $.Manufacturer -like 'FTDI' -and $.InstanceId } | ForEach-Object {
$inst = $.InstanceId
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst\Device Parameters"
if (-not (Test-Path $regPath) { New-Item -Path $regPath -Force | Out-Null }
try {
New-ItemProperty -Path $regPath -Name $prop -PropertyType DWord -Value $value -Force -ErrorAction Stop
Write-Output "Updated $inst\$prop"
} catch {
Write-Warning "Failed $inst: $($.Exception.Message)"
}
}
pnputil /scan-devices
Notes:
- Must run elevated (Admin). Enum keys are protected; some systems may return “Access denied” — paste that error and I’ll show safe next steps.
- Changes under Enum can be overwritten by driver updates or re‑enumeration. For a persistent solution use the INF AddReg method below.
- The supported way is to add an AddReg section to the driver INF so Windows writes the desired property into HKR (device instance) at install. Example AddReg (insert into the device Install section):
HKR,,<PropertyName>,0x00010001,0
; then add to the Install section:
AddReg = MyFTDI.AddReg
- Publish/install the modified INF via pnputil /add-driver and replug the device or run pnputil /scan-devices. Be aware of driver‑signing requirements for production deployments.
- Parse your FTDI_before.txt / FTDI_after.txt and tell you the exact property name and the one‑line New-ItemProperty command to run across all devices; or
- Give you the full automated diff + apply script so you can run it once and it will detect & set the property for every FTDI instance.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #25
$regPath = Join-Path $enumBase ($inst -replace '\','\') is throwing an error
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #26
The regular expression pattern \ is not valid.
At C:\Users\kioskuser2\Documents\GetDevice.ps1:3 char:1
+ $regPath = Join-Path $enumBase ($inst -replace '\','\')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (\:String) [], RuntimeException
+ FullyQualifiedErrorId : InvalidRegularExpression
Join-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\kioskuser2\Documents\GetDevice.ps1:4 char:17
+ $dp = Join-Path $regPath 'Device Parameters'
+ ~~~~~~~~
+ CategoryInfo : InvalidData: ) [Join-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCom
mand
Get-ItemProperty : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\kioskuser2\Documents\GetDevice.ps1:5 char:24
+ Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue | Format-Lis ...
+ ~~~
+ CategoryInfo : InvalidData: ) [Get-ItemProperty], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetItemProp
ertyCommand
At C:\Users\kioskuser2\Documents\GetDevice.ps1:3 char:1
+ $regPath = Join-Path $enumBase ($inst -replace '\','\')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (\:String) [], RuntimeException
+ FullyQualifiedErrorId : InvalidRegularExpression
Join-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\kioskuser2\Documents\GetDevice.ps1:4 char:17
+ $dp = Join-Path $regPath 'Device Parameters'
+ ~~~~~~~~
+ CategoryInfo : InvalidData:
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCom
mand
Get-ItemProperty : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\kioskuser2\Documents\GetDevice.ps1:5 char:24
+ Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue | Format-Lis ...
+ ~~~
+ CategoryInfo : InvalidData:
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetItemProp
ertyCommand
- Joined
- Mar 14, 2023
- Messages
- 97,513
The error comes from how you tried to build the registry path string — the backslash is a special character and your -replace expression/Join-Path call produced a null or invalid Path. The simplest fixes are either (A) avoid trying to -replace backslashes at all and interpolate $inst directly into a string, or (B) split the InstanceId on backslashes and re-join safely. Either way also check that $inst is not null.
Use one of these corrected approaches (run PowerShell as Administrator).
Option A — simplest (interpolate the InstanceId directly)
This works if $inst contains something like FTDIBUS\VID_0403+PID_6001+ABC123\0000
$inst = 'FTDIBUS\VID_0403+PID_6001+ABC123\0000' # replace with real InstanceId
$dp = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst\Device Parameters"
Write-Output "Device Parameters path = $dp"
Option B — robust (split and Join-Path)
This avoids fiddling with -replace regex escaping:
$inst = 'FTDIBUS\VID_0403+PID_6001+ABC123\0000' # replace with real InstanceId
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$parts = $inst -split '\' # splits on literal backslash
$subPath = $parts -join '\' # re-join with backslashes
$regPath = Join-Path $enumBase $subPath
$dp = Join-Path $regPath 'Device Parameters'
Write-Output "Device Parameters path = $dp"
if (Test-Path $dp) { Get-ItemProperty -Path $dp | Format-List } else { Get-ChildItem -Path $regPath -Recurse -Depth 2 | Format-List PSPath }
Full safe script to detect & write (handles null, logs, and avoids the previous error)
Run this elevated (Admin). It will find FTDI COM ports, build the correct Device Parameters path, show current values and try to write the four common selective‑suspend properties.
Add-Content -Path $log -Value "Start: $(Get-Date)"
$ports = Get-PnpDevice -Class Ports 2>$null | Where-Object { $.Manufacturer -like 'FTDI' -and $.InstanceId }
if (-not $ports) { Add-Content $log "No FTDI ports found."; throw "No FTDI ports found (run elevated)" }
foreach ($p in $ports) {
Add-Content $log "----"
Add-Content $log "FriendlyName: $($p.FriendlyName)"
Add-Content $log "InstanceId: $($p.InstanceId)"
$inst = $p.InstanceId
if ([string]::IsNullOrWhiteSpace($inst) {
Add-Content $log "InstanceId empty, skipping"
continue
}
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$parts = $inst -split '\'
$sub = $parts -join '\'
$regPath = Join-Path $enumBase $sub
$dp = Join-Path $regPath 'Device Parameters'
Add-Content $log "Computed Device Parameters: $dp"
if (Test-Path $dp) {
Add-Content $log "Current properties at $dp:"
Get-ItemProperty -Path $dp | Select-Object DeviceIdleEnabled,DefaultIdleState,UserSetDeviceIdleEnabled,SSIdleTimeout,* | Out-String | Add-Content -Path $log
} else {
Add-Content $log "Device Parameters not found under $regPath. Listing children:"
try { Get-ChildItem -Path $regPath -Recurse -Depth 2 | Select-Object -First 50 | ForEach-Object { Add-Content $log $.PSPath } } catch { Add-Content $log "Could not enumerate $regPath: $($.Exception.Message)" }
}
}
Add-Content $log "Completed: $(Get-Date)"
Write-Output "Finished. Log: $log"
Troubleshooting tips
Use one of these corrected approaches (run PowerShell as Administrator).
Option A — simplest (interpolate the InstanceId directly)
This works if $inst contains something like FTDIBUS\VID_0403+PID_6001+ABC123\0000
$inst = 'FTDIBUS\VID_0403+PID_6001+ABC123\0000' # replace with real InstanceId
$dp = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst\Device Parameters"
Write-Output "Device Parameters path = $dp"
verify then read
if (Test-Path $dp) { Get-ItemProperty -Path $dp | Format-List } else { Write-Warning "$dp not found" }Option B — robust (split and Join-Path)
This avoids fiddling with -replace regex escaping:
$inst = 'FTDIBUS\VID_0403+PID_6001+ABC123\0000' # replace with real InstanceId
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$parts = $inst -split '\' # splits on literal backslash
$subPath = $parts -join '\' # re-join with backslashes
$regPath = Join-Path $enumBase $subPath
$dp = Join-Path $regPath 'Device Parameters'
Write-Output "Device Parameters path = $dp"
if (Test-Path $dp) { Get-ItemProperty -Path $dp | Format-List } else { Get-ChildItem -Path $regPath -Recurse -Depth 2 | Format-List PSPath }
Full safe script to detect & write (handles null, logs, and avoids the previous error)
Run this elevated (Admin). It will find FTDI COM ports, build the correct Device Parameters path, show current values and try to write the four common selective‑suspend properties.
Run as Administrator
$log = "$env:USERPROFILE\Desktop\FTDI_writelog$(Get-Date -Format yyyyMMdd_HHmmss).txt"Add-Content -Path $log -Value "Start: $(Get-Date)"
$ports = Get-PnpDevice -Class Ports 2>$null | Where-Object { $.Manufacturer -like 'FTDI' -and $.InstanceId }
if (-not $ports) { Add-Content $log "No FTDI ports found."; throw "No FTDI ports found (run elevated)" }
foreach ($p in $ports) {
Add-Content $log "----"
Add-Content $log "FriendlyName: $($p.FriendlyName)"
Add-Content $log "InstanceId: $($p.InstanceId)"
$inst = $p.InstanceId
if ([string]::IsNullOrWhiteSpace($inst) {
Add-Content $log "InstanceId empty, skipping"
continue
}
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$parts = $inst -split '\'
$sub = $parts -join '\'
$regPath = Join-Path $enumBase $sub
$dp = Join-Path $regPath 'Device Parameters'
Add-Content $log "Computed Device Parameters: $dp"
if (Test-Path $dp) {
Add-Content $log "Current properties at $dp:"
Get-ItemProperty -Path $dp | Select-Object DeviceIdleEnabled,DefaultIdleState,UserSetDeviceIdleEnabled,SSIdleTimeout,* | Out-String | Add-Content -Path $log
Code:
try { New-Item -Path $dp -Force | Out-Null New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force -ErrorAction Stop New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force -ErrorAction Stop Add-Content $log "WROTE values at $dp"
} catch { Add-Content $log "ERROR writing values at $dp : $($_.Exception.Message)"
}
Add-Content $log "Device Parameters not found under $regPath. Listing children:"
try { Get-ChildItem -Path $regPath -Recurse -Depth 2 | Select-Object -First 50 | ForEach-Object { Add-Content $log $.PSPath } } catch { Add-Content $log "Could not enumerate $regPath: $($.Exception.Message)" }
}
}
Add-Content $log "Completed: $(Get-Date)"
Write-Output "Finished. Log: $log"
Troubleshooting tips
- If you still get "Cannot bind argument to parameter 'Path' because it is null", paste the exact command and error — most likely $inst was null or the variable expression inside Join-Path produced null. The script above guards against null and logs the computed path so you can see where it fails.
- Always run as Administrator — writing under HKLM\SYSTEM\CurrentControlSet\Enum needs elevated rights (sometimes SYSTEM/ACL) and will fail silently or with Access Denied otherwise.
- If Test-Path reports not found, the device may have a different instance subkey (e.g., USB\VID_... \6&...&0&0000) — the script’s enumeration output will show the actual subtree so we can target it.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #28
Can you put this as a script file that I can copy
- Joined
- Mar 14, 2023
- Messages
- 97,513
Save the following as Set-FTDI-DisableSelectiveSuspend.ps1 and run it in an elevated PowerShell (right‑click → Run as administrator).
What it does
If you want I can:
What it does
- Finds FTDI COM ports (by Manufacturer or VID_0403).
- Locates each device’s Enum\…\Device Parameters (handles the common extra 0000 subkey).
- Logs current values and writes DeviceIdleEnabled, DefaultIdleState, UserSetDeviceIdleEnabled and SSIdleTimeout = 0 (creates them if missing).
- Writes a log file to your Desktop with details and any errors.
- Run elevated. Example:
- Test/dry run (no writes): PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend.ps1 -WhatIf
- Apply changes: PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend.ps1
Code:
<#
.SYNOPSIS Disable selective-suspend settings (per-device) for FTDI COM ports by writing Device Parameters under Enum. .DESCRIPTION Finds FTDI COM ports and writes DeviceIdleEnabled, DefaultIdleState, UserSetDeviceIdleEnabled and SSIdleTimeout = 0 into each device's HKLM:\SYSTEM\CurrentControlSet\Enum\<instance>\Device Parameters or ...\0000\Device Parameters. Produces a log on the current user's Desktop. .NOTES Run as Administrator. Editing HKLM\SYSTEM\CurrentControlSet\Enum requires elevation and may be blocked by ACLs. If you get "Access denied" for Enum keys, reply and I will provide next steps (ownership/ACL alternatives or INF approach). .PARAMETER WhatIf If specified, the script will not write registry values; it will only log what it would do. .EXAMPLE # Dry run .\Set-FTDI-DisableSelectiveSuspend.ps1 -WhatIf # Apply .\Set-FTDI-DisableSelectiveSuspend.ps1
#> param( [switch]$WhatIf) function Assert-Elevated { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Error "This script must be run as Administrator. Right-click PowerShell and choose 'Run as administrator'. Exiting." exit 1 }
} Assert-Elevated $logFile = Join-Path $env:USERPROFILE ("Desktop\FTDI_DisableSelectiveSuspend_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8 try { # Discover FTDI ports (both by Manufacturer and by VID) $ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue | Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') } if (-not $ports -or $ports.Count -eq 0) { "No FTDI COM ports found by Get-PnpDevice -Class Ports." | Tee-Object -FilePath $logFile -Append "Try: Get-PnpDevice -PresentOnly -Class USB | Where-Object { \$_.InstanceId -match 'VID_0403' }" | Tee-Object -FilePath $logFile -Append throw "No FTDI COM ports found. Exiting." } "Found $($ports.Count) FTDI port(s)." | Tee-Object -FilePath $logFile -Append foreach ($p in $ports) { "----" | Tee-Object -FilePath $logFile -Append "FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append "InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append $inst = $p.InstanceId if ([string]::IsNullOrWhiteSpace($inst) { "InstanceId is empty or null; skipping." | Tee-Object -FilePath $logFile -Append continue } # Build the registry Enum base path robustly $enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum' $parts = $inst -split '\\' # split on literal backslash $sub = $parts -join '\' # re-join safely $regPathBase = Join-Path $enumBase $sub # Candidate Device Parameters locations (common variants) $candidates = @( Join-Path $regPathBase 'Device Parameters', Join-Path $regPathBase '0000\Device Parameters') $found = $false foreach ($dp in $candidates) { "Checking: $dp" | Tee-Object -FilePath $logFile -Append if (Test-Path $dp) { $found = $true "FOUND Device Parameters at: $dp" | Tee-Object -FilePath $logFile -Append # Read current properties (if any) try { $curr = Get-ItemProperty -Path $dp -ErrorAction Stop $sel = @{ DeviceIdleEnabled = $curr.DeviceIdleEnabled; DefaultIdleState = $curr.DefaultIdleState; UserSetDeviceIdleEnabled = $curr.UserSetDeviceIdleEnabled; SSIdleTimeout = $curr.SSIdleTimeout } "Current values: $($sel | ConvertTo-Json -Depth 2)" | Tee-Object -FilePath $logFile -Append } catch { "Could not read properties at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } if ($WhatIf) { "WhatIf: would write DeviceIdleEnabled=0, DefaultIdleState=0, UserSetDeviceIdleEnabled=0, SSIdleTimeout=0 at $dp" | Tee-Object -FilePath $logFile -Append } else { try { # Ensure key exists and write values New-Item -Path $dp -Force | Out-Null New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE values at $dp" | Tee-Object -FilePath $logFile -Append } catch { "ERROR writing values at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } break # stop checking candidates for this device } else { "Not found: $dp" | Tee-Object -FilePath $logFile -Append } } if (-not $found) { "Device Parameters not found under $regPathBase. Listing up to 50 child keys for diagnosis:" | Tee-Object -FilePath $logFile -Append try { Get-ChildItem -Path $regPathBase -Recurse -Depth 2 -ErrorAction Stop | Select-Object -First 50 | ForEach-Object { $_.PSPath } | Tee-Object -FilePath $logFile -Append } catch { "Could not enumerate $regPathBase : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } # Suggest rescan if we performed changes if (-not $WhatIf) { "If you applied changes, run 'pnputil /scan-devices' or reboot to have Windows pick up the new values." | Tee-Object -FilePath $logFile -Append } else { "Dry run completed (WhatIf). No registry writes performed." | Tee-Object -FilePath $logFile -Append } } catch { "Unhandled error: $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append throw
} finally { "Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
} Write-Output "Done. Log saved to: $logFile"
if (-not $WhatIf) { Write-Output "If you wrote values, run: pnputil /scan-devices (or reboot) and then check the COM port Advanced → Serial Enumerator state in Device Manager." }
- Also produce a one‑line command to push this to multiple PCs via PSRemoting / Intune / SCCM; or
- Produce a variant that writes a different property name (e.g., if the Device Manager toggled a different vendor property), once you paste the before/after diff output from a single instance.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #30
add in disable serial enumerator as well
- Joined
- Mar 14, 2023
- Messages
- 97,513
Done — I updated the script to also detect and disable any “Serial Enumerator” style properties for each FTDI device instance.
What I changed
Script (copy whole file and save as .ps1):
Notes & cautions
What I changed
- The script still writes DeviceIdleEnabled / DefaultIdleState / UserSetDeviceIdleEnabled / SSIdleTimeout = 0 as before.
- It now also:
- looks for property names in each device’s Device Parameters that match serial/enum patterns (case‑insensitive), and
- sets common explicit names if present (SerialEnumerator, EnableSerialEnumerator, SerialEnum, SerialEnumerate, etc., creating them if missing.
- The script keeps the existing -WhatIf switch (dry run) and logs every action to a Desktop log file.
Script (copy whole file and save as .ps1):
Code:
<#
.SYNOPSIS Disable per-device selective-suspend and disable Serial Enumerator-like properties for FTDI COM ports. .DESCRIPTION Finds FTDI COM ports and writes DeviceIdleEnabled, DefaultIdleState, UserSetDeviceIdleEnabled and SSIdleTimeout = 0 into each device's HKLM:\SYSTEM\CurrentControlSet\Enum\<instance>\Device Parameters (or ...\0000\Device Parameters). Also detects property names that look like "Serial Enumerator" and sets them = 0 (creates if missing). Produces a log on the current user's Desktop. .PARAMETER WhatIf If specified, the script will not write registry values; it will only log what it would do. .EXAMPLE # Dry run .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1 -WhatIf # Apply .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1
#> param( [switch]$WhatIf) function Assert-Elevated { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Error "This script must be run as Administrator. Right-click PowerShell and choose 'Run as administrator'. Exiting." exit 1 }
} Assert-Elevated $logFile = Join-Path $env:USERPROFILE ("Desktop\FTDI_DisableSelectiveSuspend_SerialEnum_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8 # Explicit names we will set (if present) and names to try creating
$explicitSerialNames = @( 'SerialEnumerator','EnableSerialEnumerator','SerialEnum','SerialEnumerate','SerialEnumer','EnableSerialEnumer','SerialEnumeratorEnabled') # Regex to detect vendor-specific property names containing serial + enum (loose)
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)' try { # Discover FTDI ports (both by Manufacturer and by VID) $ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue | Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') } if (-not $ports -or $ports.Count -eq 0) { "No FTDI COM ports found by Get-PnpDevice -Class Ports." | Tee-Object -FilePath $logFile -Append "Try: Get-PnpDevice -PresentOnly -Class USB | Where-Object { \$_.InstanceId -match 'VID_0403' }" | Tee-Object -FilePath $logFile -Append throw "No FTDI COM ports found. Exiting." } "Found $($ports.Count) FTDI port(s)." | Tee-Object -FilePath $logFile -Append foreach ($p in $ports) { "----" | Tee-Object -FilePath $logFile -Append "FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append "InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append $inst = $p.InstanceId if ([string]::IsNullOrWhiteSpace($inst) { "InstanceId is empty or null; skipping." | Tee-Object -FilePath $logFile -Append continue } # Build the registry Enum base path robustly $enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum' $parts = $inst -split '\\' $sub = $parts -join '\' $regPathBase = Join-Path $enumBase $sub # Candidate Device Parameters locations $candidates = @( Join-Path $regPathBase 'Device Parameters', Join-Path $regPathBase '0000\Device Parameters') $found = $false foreach ($dp in $candidates) { "Checking: $dp" | Tee-Object -FilePath $logFile -Append if (Test-Path $dp) { $found = $true "FOUND Device Parameters at: $dp" | Tee-Object -FilePath $logFile -Append # Read current properties (if any) try { $curr = Get-ItemProperty -Path $dp -ErrorAction Stop # capture some known keys plus list all property names for detection $currProps = $curr.PSObject.Properties | ForEach-Object { $_.Name } $sel = @{ DeviceIdleEnabled = ($curr.DeviceIdleEnabled -as [int]); DefaultIdleState = ($curr.DefaultIdleState -as [int]); UserSetDeviceIdleEnabled = ($curr.UserSetDeviceIdleEnabled -as [int]); SSIdleTimeout = ($curr.SSIdleTimeout -as [int]); AllProperties = $currProps } "Current values: $($sel | ConvertTo-Json -Depth 3)" | Tee-Object -FilePath $logFile -Append } catch { "Could not read properties at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $currProps = @ } if ($WhatIf) { "WhatIf: would write DeviceIdleEnabled=0, DefaultIdleState=0, UserSetDeviceIdleEnabled=0, SSIdleTimeout=0 at $dp" | Tee-Object -FilePath $logFile -Append } else { try { # Ensure key exists and write values New-Item -Path $dp -Force | Out-Null New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE selective-suspend values at $dp" | Tee-Object -FilePath $logFile -Append } catch { "ERROR writing selective-suspend values at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } # ---- Serial Enumerator detection + write ---- # 1) Explicit names: set any of the common names if present (or create them) foreach ($name in $explicitSerialNames) { if ($WhatIf) { "WhatIf: would set $name = 0 at $dp (explicit list)" | Tee-Object -FilePath $logFile -Append } else { try { New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE $name = 0 at $dp (explicit list)" | Tee-Object -FilePath $logFile -Append } catch { "Failed to write $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } # 2) Regex-based detection among existing property names (do not create arbitrary unknown names unless explicit) $matched = @ foreach ($propName in $currProps) { if ($propName -match $serialEnumRegex) { $matched += $propName } } if ($matched.Count -gt 0) { foreach ($m in $matched) { if ($WhatIf) { "WhatIf: would set detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append } else { try { New-ItemProperty -Path $dp -Name $m -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append } catch { "Failed to write detected property $m at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } } else { "No existing properties matched serial/enum regex at $dp." | Tee-Object -FilePath $logFile -Append } break # stop checking candidates for this device } else { "Not found: $dp" | Tee-Object -FilePath $logFile -Append } } if (-not $found) { "Device Parameters not found under $regPathBase. Listing up to 50 child keys for diagnosis:" | Tee-Object -FilePath $logFile -Append try { Get-ChildItem -Path $regPathBase -Recurse -Depth 2 -ErrorAction Stop | Select-Object -First 50 | ForEach-Object { $_.PSPath } | Tee-Object -FilePath $logFile -Append } catch { "Could not enumerate $regPathBase : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } # Suggest rescan if we performed changes if (-not $WhatIf) { "If you applied changes, run 'pnputil /scan-devices' or reboot to have Windows pick up the new values." | Tee-Object -FilePath $logFile -Append } else { "Dry run completed (WhatIf). No registry writes performed." | Tee-Object -FilePath $logFile -Append } } catch { "Unhandled error: $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append throw
} finally { "Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
} Write-Output "Done. Log saved to: $logFile"
if (-not $WhatIf) { Write-Output "If you wrote values, run: pnputil /scan-devices (or reboot) and then check the COM port Advanced → Serial Enumerator state in Device Manager." }
- Run as Administrator. Writing under HKLM\SYSTEM\CurrentControlSet\Enum may be blocked by ACLs on some systems — the script logs failures. If you get “Access denied” for Enum keys, paste the log and I’ll provide safe next steps (ownership/ACL options or INF approach).
- The script attempts to be conservative: it sets explicit commonly used serial‑enumerator names and also sets any existing property that looks like serial+enum (based on regex). It will not create arbitrary unknown names except the explicit list above (which includes common vendor property names).
- INF AddReg remains the recommended persistent approach if driver re-enumeration overwrites these changes.
- Tweak the explicit name list (add or remove names) before you run; or
- Produce a version that only changes detected regex matches (no explicit creation), or
- Produce the INF AddReg snippet ready to paste into your oem167.inf.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #32
Join-Path : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ChildPath'. Specified
method is not supported.
At C:\Users\kioskuser2\Documents\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1:79 char:30
+ Join-Path $regPathBase 'Device Parameters',
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: ) [Join-Path], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.JoinPathCommand
method is not supported.
At C:\Users\kioskuser2\Documents\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1:79 char:30
+ Join-Path $regPathBase 'Device Parameters',
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument:
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.JoinPathCommand
- Joined
- Mar 14, 2023
- Messages
- 97,513
Thanks — that error means PowerShell got an array where Join-Path expected a string. The fix is to guarantee the pieces are strings and to build the candidate paths safely (don't pass a backslash-containing childPath array to Join-Path).
I fixed that in the script and simplified the Device Parameters candidate paths so Join-Path is always called with string args. Save the entire block below as Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1 and run it elevated.
Copy the whole file and run (Admin):
Notes
I fixed that in the script and simplified the Device Parameters candidate paths so Join-Path is always called with string args. Save the entire block below as Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1 and run it elevated.
Copy the whole file and run (Admin):
- Dry run: PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1 -WhatIf
- Apply: PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator.ps1
Code:
<#
Disable per-device selective-suspend and disable Serial Enumerator-like properties for FTDI COM ports.
Run as Administrator.
#> param( [switch]$WhatIf) function Assert-Elevated { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Error "This script must be run as Administrator. Right-click PowerShell and choose 'Run as administrator'. Exiting." exit 1 }
} Assert-Elevated $logFile = Join-Path $env:USERPROFILE ("Desktop\FTDI_DisableSelectiveSuspend_SerialEnum_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8 $explicitSerialNames = @( 'SerialEnumerator','EnableSerialEnumerator','SerialEnum','SerialEnumerate','SerialEnumer','EnableSerialEnumer','SerialEnumeratorEnabled')
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)' try { $ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue | Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') } if (-not $ports -or $ports.Count -eq 0) { "No FTDI COM ports found by Get-PnpDevice -Class Ports." | Tee-Object -FilePath $logFile -Append throw "No FTDI COM ports found. Exiting." } "Found $($ports.Count) FTDI port(s)." | Tee-Object -FilePath $logFile -Append foreach ($p in $ports) { "----" | Tee-Object -FilePath $logFile -Append "FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append "InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append $inst = $p.InstanceId if ([string]::IsNullOrWhiteSpace($inst) { "InstanceId is empty or null; skipping." | Tee-Object -FilePath $logFile -Append continue } $enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum' # Ensure $sub is a single string $parts = $inst -split '\\' $sub = [string]($parts -join '\') $regPathBase = Join-Path $enumBase $sub # Build candidate Device Parameters paths safely $candidates = @ $candidates += Join-Path $regPathBase 'Device Parameters' $candidates += Join-Path (Join-Path $regPathBase '0000') 'Device Parameters' $found = $false foreach ($dp in $candidates) { "Checking: $dp" | Tee-Object -FilePath $logFile -Append if (Test-Path $dp) { $found = $true "FOUND Device Parameters at: $dp" | Tee-Object -FilePath $logFile -Append try { $curr = Get-ItemProperty -Path $dp -ErrorAction Stop $currProps = $curr.PSObject.Properties | ForEach-Object { $_.Name } $sel = @{ DeviceIdleEnabled = ($curr.DeviceIdleEnabled -as [int]); DefaultIdleState = ($curr.DefaultIdleState -as [int]); UserSetDeviceIdleEnabled = ($curr.UserSetDeviceIdleEnabled -as [int]); SSIdleTimeout = ($curr.SSIdleTimeout -as [int]); AllProperties = $currProps } "Current values: $($sel | ConvertTo-Json -Depth 3)" | Tee-Object -FilePath $logFile -Append } catch { "Could not read properties at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $currProps = @ } if ($WhatIf) { "WhatIf: would write DeviceIdleEnabled=0, DefaultIdleState=0, UserSetDeviceIdleEnabled=0, SSIdleTimeout=0 at $dp" | Tee-Object -FilePath $logFile -Append } else { try { New-Item -Path $dp -Force | Out-Null New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE selective-suspend values at $dp" | Tee-Object -FilePath $logFile -Append } catch { "ERROR writing selective-suspend values at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } # Explicit names: create/set them to 0 foreach ($name in $explicitSerialNames) { if ($WhatIf) { "WhatIf: would set $name = 0 at $dp (explicit list)" | Tee-Object -FilePath $logFile -Append } else { try { New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE $name = 0 at $dp (explicit list)" | Tee-Object -FilePath $logFile -Append } catch { "Failed to write $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } # Regex detection among existing property names $matched = @ foreach ($propName in $currProps) { if ($propName -match $serialEnumRegex) { $matched += $propName } } if ($matched.Count -gt 0) { foreach ($m in $matched) { if ($WhatIf) { "WhatIf: would set detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append } else { try { New-ItemProperty -Path $dp -Name $m -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append } catch { "Failed to write detected property $m at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } } else { "No existing properties matched serial/enum regex at $dp." | Tee-Object -FilePath $logFile -Append } break } else { "Not found: $dp" | Tee-Object -FilePath $logFile -Append } } if (-not $found) { "Device Parameters not found under $regPathBase. Listing up to 50 child keys for diagnosis:" | Tee-Object -FilePath $logFile -Append try { Get-ChildItem -Path $regPathBase -Recurse -Depth 2 -ErrorAction Stop | Select-Object -First 50 | ForEach-Object { $_.PSPath } | Tee-Object -FilePath $logFile -Append } catch { "Could not enumerate $regPathBase : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } if (-not $WhatIf) { "If you applied changes, run 'pnputil /scan-devices' or reboot to have Windows pick up the new values." | Tee-Object -FilePath $logFile -Append } else { "Dry run completed (WhatIf). No registry writes performed." | Tee-Object -FilePath $logFile -Append } } catch { "Unhandled error: $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append throw
} finally { "Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
} Write-Output "Done. Log saved to: $logFile"
if (-not $WhatIf) { Write-Output "If you wrote values, run: pnputil /scan-devices (or reboot) and then check the COM port Advanced → Serial Enumerator state in Device Manager." }
- The key fix: we force $sub to be a single string ([string]($parts -join '\') and we build the '0000\Device Parameters' path by joining '0000' first and then 'Device Parameters' so Join-Path never receives an array as any parameter.
- If you still see an error, paste the exact console error plus one sample InstanceId from Get-PnpDevice output and I’ll debug the exact failing value.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #34
Can you add force com port number to a specific value to this script
# Run PowerShell as Administrator
# get FTDI ports (adjust filter as needed)
$ports = Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like '*FTDI*' -and $_.InstanceId }
foreach ($p in $ports) {
$inst = $p.InstanceId
Write-Output "Device InstanceId: $inst"
# search Enum for keys that include the InstanceId (case-insensitive)
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$matches = Get-ChildItem -Path $enumBase -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -like "*$($inst.Split('\')[-1])*" -or $_.PSPath -match [regex]::Escape($inst) }
foreach ($m in $matches) {
# possible Device Parameters locations
$dp1 = Join-Path $m.PSPath 'Device Parameters'
$dp2 = Join-Path $m.PSPath '0000\Device Parameters'
foreach ($dp in @($dp1, $dp2)) {
if (Test-Path $dp) {
Write-Output "Updating: $dp"
New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force | Out-Null
}
}
}
}
# rescan devices so Windows sees changes (requires devcon or pnputil)
# devcon rescan <-- if devcon available
pnputil /scan-devices
# Run PowerShell as Administrator
# get FTDI ports (adjust filter as needed)
$ports = Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like '*FTDI*' -and $_.InstanceId }
foreach ($p in $ports) {
$inst = $p.InstanceId
Write-Output "Device InstanceId: $inst"
# search Enum for keys that include the InstanceId (case-insensitive)
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$matches = Get-ChildItem -Path $enumBase -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -like "*$($inst.Split('\')[-1])*" -or $_.PSPath -match [regex]::Escape($inst) }
foreach ($m in $matches) {
# possible Device Parameters locations
$dp1 = Join-Path $m.PSPath 'Device Parameters'
$dp2 = Join-Path $m.PSPath '0000\Device Parameters'
foreach ($dp in @($dp1, $dp2)) {
if (Test-Path $dp) {
Write-Output "Updating: $dp"
New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force | Out-Null
}
}
}
}
# rescan devices so Windows sees changes (requires devcon or pnputil)
# devcon rescan <-- if devcon available
pnputil /scan-devices
- Joined
- Mar 14, 2023
- Messages
- 97,513
Updated script saved as Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1
Save the entire block below as Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 and run it in an elevated PowerShell (right‑click → Run as administrator).
Summary of features added
If you want I can:
Save the entire block below as Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 and run it in an elevated PowerShell (right‑click → Run as administrator).
Summary of features added
- Existing behavior: finds FTDI COM ports, writes DeviceIdleEnabled/DefaultIdleState/UserSetDeviceIdleEnabled/SSIdleTimeout = 0 and tries to disable Serial Enumerator‑style properties.
- New behavior: can force COM port assignment:
- Use -StartCom <int> to assign sequential COM numbers starting at that integer (will increment per FTDI device).
- Or use -ForceCom "COMn" to try to set the same COM name for every matched device (you probably don’t want that, unless you understand conflicts).
- Use -Override to force assignment even if the desired COM is already in use (dangerous — may cause conflicts).
- Dry run mode: -WhatIf (no writes, only logs).
- Logs everything to a timestamped file on your Desktop.
- You must run elevated.
- Changing COM assignments can conflict with existing ports. By default the script will skip devices if the desired COM is already in use (unless you pass -Override).
- After applying changes run pnputil /scan-devices or reboot to let Windows re-enumerate ports.
- This script uses the registry Device Parameters PortName to set the COM name — this works on most FTDI devices but driver/INF may override the value on re-install; persistent solution is INF AddReg (I can help with that if needed).
Code:
<#
Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 Usage (run elevated): # Dry run .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -WhatIf # Apply and assign COMs sequentially starting at COM10 .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -StartCom 10 # Apply and attempt to force all FTDI devices to COM5 (may conflict) .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -ForceCom 'COM5' -Override Parameters: -WhatIf : dry run, no registry writes -StartCom : integer, starting COM number (will increment per device) -ForceCom : string "COMn" to set that exact COM name for each device -Override : if specified, allow overriding existing COM owners (use with caution)
#> param( [switch]$WhatIf, [int]$StartCom, [string]$ForceCom, [switch]$Override) function Assert-Elevated { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Error "This script must be run as Administrator. Right-click PowerShell and choose 'Run as administrator'. Exiting." exit 1 }
}
Assert-Elevated $logFile = Join-Path $env:USERPROFILE ("Desktop\FTDI_Update_COM_and_Power_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8 # Explicit serial-enumerator property names to create if desired
$explicitSerialNames = @( 'SerialEnumerator','EnableSerialEnumerator','SerialEnum','SerialEnumerate','SerialEnumer','EnableSerialEnumer','SerialEnumeratorEnabled')
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)' # Helper: check if COM name in use (returns array of DeviceIDs using that COM)
function Get-ComOwners { param($comName) # Query Win32_SerialPort DeviceID (COMx) try { $owners = Get-CimInstance -ClassName Win32_SerialPort -ErrorAction SilentlyContinue | Where-Object { $_.DeviceID -eq $comName } return $owners } catch { return @ }
} # Discover FTDI ports
try { $ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue | Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') } if (-not $ports -or $ports.Count -eq 0) { "No FTDI COM ports found by Get-PnpDevice -Class Ports." | Tee-Object -FilePath $logFile -Append throw "No FTDI COM ports found. Exiting." } "Found $($ports.Count) FTDI port(s)." | Tee-Object -FilePath $logFile -Append # Prepare COM assignment counters $nextCom = if ($PSBoundParameters.ContainsKey('StartCom') { [int]$StartCom } else { $null } foreach ($p in $ports) { "----" | Tee-Object -FilePath $logFile -Append "FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append "InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append $inst = $p.InstanceId if ([string]::IsNullOrWhiteSpace($inst) { "InstanceId is empty or null; skipping." | Tee-Object -FilePath $logFile -Append continue } # Build enum base/reg path $enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum' $parts = $inst -split '\\' $sub = [string]($parts -join '\') $regPathBase = Join-Path $enumBase $sub # Candidate Device Parameters paths $candidates = @ $candidates += Join-Path $regPathBase 'Device Parameters' $candidates += Join-Path (Join-Path $regPathBase '0000') 'Device Parameters' $found = $false foreach ($dp in $candidates) { "Checking: $dp" | Tee-Object -FilePath $logFile -Append if (Test-Path $dp) { $found = $true "FOUND Device Parameters at: $dp" | Tee-Object -FilePath $logFile -Append # Read current properties try { $curr = Get-ItemProperty -Path $dp -ErrorAction Stop $currProps = $curr.PSObject.Properties | ForEach-Object { $_.Name } $currPort = $curr.PortName "Current PortName: $currPort" | Tee-Object -FilePath $logFile -Append } catch { "Could not read properties at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $currProps = @ $currPort = $null } # Write selective-suspend values (or WhatIf) if ($WhatIf) { "WhatIf: would set DeviceIdleEnabled/DefaultIdleState/UserSetDeviceIdleEnabled/SSIdleTimeout = 0 at $dp" | Tee-Object -FilePath $logFile -Append } else { try { New-Item -Path $dp -Force | Out-Null New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE selective-suspend values at $dp" | Tee-Object -FilePath $logFile -Append } catch { "ERROR writing selective-suspend values at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } # Disable Serial Enumerator properties foreach ($name in $explicitSerialNames) { if ($WhatIf) { "WhatIf: would set $name = 0 at $dp (explicit list)" | Tee-Object -FilePath $logFile -Append } else { try { New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE $name = 0 at $dp (explicit list)" | Tee-Object -FilePath $logFile -Append } catch { "Failed to write $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } $matched = @ foreach ($propName in $currProps) { if ($propName -match $serialEnumRegex) { $matched += $propName } } if ($matched.Count -gt 0) { foreach ($m in $matched) { if ($WhatIf) { "WhatIf: would set detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append } else { try { New-ItemProperty -Path $dp -Name $m -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append } catch { "Failed to write detected property $m at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } } else { "No existing properties matched serial/enum regex at $dp." | Tee-Object -FilePath $logFile -Append } # ---- COM assignment logic ---- $desiredCom = $null if ($PSBoundParameters.ContainsKey('ForceCom') -and -not [string]::IsNullOrWhiteSpace($ForceCom) { $desiredCom = $ForceCom } elseif ($PSBoundParameters.ContainsKey('StartCom') -and $null -ne $nextCom) { $desiredCom = "COM$nextCom" } else { $desiredCom = $null } if ($desiredCom) { "Desired COM for this device: $desiredCom" | Tee-Object -FilePath $logFile -Append # check current owner(s) $owners = Get-ComOwners -comName $desiredCom $inUse = ($owners -and $owners.Count -gt 0) # if current port already equals desired, skip if ($currPort -and ($currPort -eq $desiredCom) { "Device already assigned $desiredCom (no change)." | Tee-Object -FilePath $logFile -Append } elseif ($inUse -and -not $Override) { "Desired $desiredCom is already in use by another device. Skipping (use -Override to force)." | Tee-Object -FilePath $logFile -Append } else { if ($WhatIf) { "WhatIf: would set PortName = $desiredCom at $dp" | Tee-Object -FilePath $logFile -Append if ($PSBoundParameters.ContainsKey('StartCom') { $nextCom = $nextCom + 1 } } else { try { # write PortName New-ItemProperty -Path $dp -Name 'PortName' -PropertyType String -Value $desiredCom -Force -ErrorAction Stop | Out-Null "WROTE PortName = $desiredCom at $dp" | Tee-Object -FilePath $logFile -Append # optionally: record that we changed it (no attempt to update HARDWARE\DEVICEMAP\SERIALCOMM here) "Note: after changing PortName, run 'pnputil /scan-devices' or reboot to let Windows register the COM change." | Tee-Object -FilePath $logFile -Append if ($PSBoundParameters.ContainsKey('StartCom') { $nextCom = $nextCom + 1 } } catch { "Failed to set PortName at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } } else { "No COM assignment requested for this run." | Tee-Object -FilePath $logFile -Append } break # processed this device } else { "Not found: $dp" | Tee-Object -FilePath $logFile -Append } } if (-not $found) { "Device Parameters not found under $regPathBase. Listing up to 50 child keys for diagnosis:" | Tee-Object -FilePath $logFile -Append try { Get-ChildItem -Path $regPathBase -Recurse -Depth 2 -ErrorAction Stop | Select-Object -First 50 | ForEach-Object { $_.PSPath } | Tee-Object -FilePath $logFile -Append } catch { "Could not enumerate $regPathBase : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } } } if (-not $WhatIf) { "If you applied changes, run 'pnputil /scan-devices' or reboot to have Windows pick up the new values." | Tee-Object -FilePath $logFile -Append } else { "Dry run completed (WhatIf). No registry writes performed." | Tee-Object -FilePath $logFile -Append } } catch { "Unhandled error: $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append throw
} finally { "Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
} Write-Output "Done. Log saved to: $logFile"
if (-not $WhatIf) { Write-Output "If you wrote values, run: pnputil /scan-devices (or reboot) and then check the COM port Advanced → Serial Enumerator state in Device Manager." }
- Remove creation of explicit serial property names and only set detected regex matches (safer); or
- Add code that updates HARDWARE\DEVICEMAP\SERIALCOMM entries as well (riskier); or
- Add an option to assign COMs by pattern (e.g., port per USB port index).
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #36
Also only makes the changes if they do not exist already. let user know if no updates were needed.
- Joined
- Mar 14, 2023
- Messages
- 97,513
Got it — I updated the script so it only writes registry values when the desired value is not already present, and it reports which devices were changed (or that no updates were needed). It still supports:
Usage examples
Notes
- disabling selective‑suspend per device (DeviceIdleEnabled, DefaultIdleState, UserSetDeviceIdleEnabled, SSIdleTimeout),
- disabling Serial Enumerator style properties (explicit list + regex detection),
- forcing COM assignment (StartCom sequential or ForceCom single value) with optional -Override,
- dry run mode (-WhatIf),
- full logging to a timestamped file on your Desktop.
Usage examples
- Dry run (no writes):
PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -WhatIf - Apply only selective-suspend + serial enumerator changes (no COM assignment):
PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 - Apply and assign sequential COMs starting at COM10:
PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -StartCom 10 - Force every FTDI device to COM5 (use -Override to force even if in use — dangerous):
PowerShell -ExecutionPolicy Bypass -File .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -ForceCom 'COM5' -Override
Code:
<#
Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 Idempotent: only writes properties when the desired value is not already present.
Reports devices changed and those already compliant. Run as Administrator. Parameters: -WhatIf : dry run, no registry writes -StartCom : integer, starting COM number (sequential assignment) -ForceCom : string "COMn" to try to set that exact COM name for devices -Override : allow overriding an existing COM owner (dangerous) Examples: # Dry-run .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -WhatIf # Apply and assign COMs starting at 10 .\Set-FTDI-DisableSelectiveSuspend-And-SerialEnumerator-And-ForceCOM.ps1 -StartCom 10
#> param( [switch]$WhatIf, [int]$StartCom, [string]$ForceCom, [switch]$Override) function Assert-Elevated { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Error "This script must be run as Administrator. Right-click PowerShell and choose 'Run as administrator'. Exiting." exit 1 }
}
Assert-Elevated $logFile = Join-Path $env:USERPROFILE ("Desktop\FTDI_Update_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8 # Names used to detect/create serial enumerator flags
$explicitSerialNames = @( 'SerialEnumerator','EnableSerialEnumerator','SerialEnum','SerialEnumerate','SerialEnumer','EnableSerialEnumer','SerialEnumeratorEnabled')
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)' # Tracking for summary
$changedDevices = @
$skippedDevices = @
$errors = @ function Get-ComOwners { param($comName) try { Get-CimInstance -ClassName Win32_SerialPort -ErrorAction SilentlyContinue | Where-Object { $_.DeviceID -eq $comName } } catch { @ }
} try { $ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue | Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') } if (-not $ports -or $ports.Count -eq 0) { "No FTDI COM ports found by Get-PnpDevice -Class Ports." | Tee-Object -FilePath $logFile -Append throw "No FTDI COM ports found. Exiting." } "Found $($ports.Count) FTDI port(s)." | Tee-Object -FilePath $logFile -Append $nextCom = if ($PSBoundParameters.ContainsKey('StartCom') { [int]$StartCom } else { $null } foreach ($p in $ports) { "----" | Tee-Object -FilePath $logFile -Append "FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append "InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append $inst = $p.InstanceId if ([string]::IsNullOrWhiteSpace($inst) { "InstanceId empty; skipping." | Tee-Object -FilePath $logFile -Append continue } # Build registry base $enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum' $parts = $inst -split '\\' $sub = [string]($parts -join '\') $regPathBase = Join-Path $enumBase $sub $candidates = @ $candidates += Join-Path $regPathBase 'Device Parameters' $candidates += Join-Path (Join-Path $regPathBase '0000') 'Device Parameters' $foundDP = $false $deviceChanged = $false foreach ($dp in $candidates) { "Checking: $dp" | Tee-Object -FilePath $logFile -Append if (Test-Path $dp) { $foundDP = $true "FOUND Device Parameters at: $dp" | Tee-Object -FilePath $logFile -Append # read current properties try { $curr = Get-ItemProperty -Path $dp -ErrorAction Stop $currProps = $curr.PSObject.Properties | ForEach-Object { $_.Name } $currPort = if ($curr.PSObject.Properties.Name -contains 'PortName') { $curr.PortName } else { $null } "Existing PortName: $currPort" | Tee-Object -FilePath $logFile -Append } catch { "Could not read $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $currProps = @ $currPort = $null } # Helper to test-and-write a DWORD property only if needed function Ensure-Dword { param($Path, $Name, $DesiredValue) $write = $false try { $existing = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $Name -ErrorAction SilentlyContinue } catch { $existing = $null } if ($null -eq $existing) { $write = $true } elseif ([int]$existing -ne [int]$DesiredValue) { $write = $true } if ($write) { if ($WhatIf) { "WhatIf: would set $Name = $DesiredValue at $Path" | Tee-Object -FilePath $logFile -Append return $true } else { try { New-Item -Path $Path -Force | Out-Null New-ItemProperty -Path $Path -Name $Name -PropertyType DWord -Value $DesiredValue -Force -ErrorAction Stop | Out-Null "WROTE $Name = $DesiredValue at $Path" | Tee-Object -FilePath $logFile -Append return $true } catch { "ERROR writing $Name at $Path : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $errors += "ERROR writing $Name at $Path : $($_.Exception.Message)" return $false } } } else { "$Name already = $existing at $Path (no change)" | Tee-Object -FilePath $logFile -Append return $false } } # selective-suspend properties to ensure = 0 $sv1 = Ensure-Dword -Path $dp -Name 'DeviceIdleEnabled' -DesiredValue 0 $sv2 = Ensure-Dword -Path $dp -Name 'DefaultIdleState' -DesiredValue 0 $sv3 = Ensure-Dword -Path $dp -Name 'UserSetDeviceIdleEnabled' -DesiredValue 0 $sv4 = Ensure-Dword -Path $dp -Name 'SSIdleTimeout' -DesiredValue 0 if ($sv1 -or $sv2 -or $sv3 -or $sv4) { $deviceChanged = $true } # explicit serial enumerator names: only create/update if needed foreach ($name in $explicitSerialNames) { $w = $false try { $existing = Get-ItemProperty -Path $dp -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name -ErrorAction SilentlyContinue } catch { $existing = $null } if ($null -eq $existing -or [int]$existing -ne 0) { if ($WhatIf) { "WhatIf: would set $name = 0 at $dp (explicit)" | Tee-Object -FilePath $logFile -Append $w = $true } else { try { New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE $name = 0 at $dp (explicit)" | Tee-Object -FilePath $logFile -Append $w = $true } catch { "Failed to write $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $errors += "Failed to write $name at $dp : $($_.Exception.Message)" } } } else { "$name already = $existing at $dp (no change)" | Tee-Object -FilePath $logFile -Append } if ($w) { $deviceChanged = $true } } # regex detection among existing property names (only set existing matches) $matched = @ foreach ($propName in $currProps) { if ($propName -match $serialEnumRegex) { $matched += $propName } } if ($matched.Count -gt 0) { foreach ($m in $matched) { try { $existing = Get-ItemProperty -Path $dp -Name $m -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $m -ErrorAction SilentlyContinue } catch { $existing = $null } if ($null -eq $existing -or [int]$existing -ne 0) { if ($WhatIf) { "WhatIf: would set detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append $deviceChanged = $true } else { try { New-ItemProperty -Path $dp -Name $m -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "WROTE detected property $m = 0 at $dp (regex match)" | Tee-Object -FilePath $logFile -Append $deviceChanged = $true } catch { "Failed to write detected property $m at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $errors += "Failed to write detected property $m at $dp : $($_.Exception.Message)" } } } else { "$m already = $existing at $dp (no change)" | Tee-Object -FilePath $logFile -Append } } } else { "No existing properties matched serial/enum regex at $dp." | Tee-Object -FilePath $logFile -Append } # COM assignment logic (only write when desired COM different from current) $desiredCom = $null if ($PSBoundParameters.ContainsKey('ForceCom') -and -not [string]::IsNullOrWhiteSpace($ForceCom) { $desiredCom = $ForceCom } elseif ($PSBoundParameters.ContainsKey('StartCom') -and $null -ne $nextCom) { $desiredCom = "COM$nextCom" } if ($desiredCom) { "Desired COM: $desiredCom" | Tee-Object -FilePath $logFile -Append $owners = Get-ComOwners -comName $desiredCom $inUse = ($owners -and $owners.Count -gt 0) if ($currPort -and ($currPort -eq $desiredCom) { "Device already assigned $desiredCom (no change)." | Tee-Object -FilePath $logFile -Append } elseif ($inUse -and -not $Override) { "Desired $desiredCom is in use; skipping (use -Override to force)." | Tee-Object -FilePath $logFile -Append } else { if ($WhatIf) { "WhatIf: would set PortName = $desiredCom at $dp" | Tee-Object -FilePath $logFile -Append $deviceChanged = $true if ($PSBoundParameters.ContainsKey('StartCom') { $nextCom = $nextCom + 1 } } else { try { New-ItemProperty -Path $dp -Name 'PortName' -PropertyType String -Value $desiredCom -Force -ErrorAction Stop | Out-Null "WROTE PortName = $desiredCom at $dp" | Tee-Object -FilePath $logFile -Append $deviceChanged = $true if ($PSBoundParameters.ContainsKey('StartCom') { $nextCom = $nextCom + 1 } } catch { "Failed to set PortName at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $errors += "Failed to set PortName at $dp : $($_.Exception.Message)" } } } } else { "No COM assignment requested for this device." | Tee-Object -FilePath $logFile -Append } if ($deviceChanged) { $changedDevices += $inst } else { $skippedDevices += $inst } break } else { "Not found: $dp" | Tee-Object -FilePath $logFile -Append } } if (-not $foundDP) { "Device Parameters not found under $regPathBase. Listing children for diagnosis:" | Tee-Object -FilePath $logFile -Append try { Get-ChildItem -Path $regPathBase -Recurse -Depth 2 -ErrorAction Stop | Select-Object -First 50 | ForEach-Object { $_.PSPath } | Tee-Object -FilePath $logFile -Append } catch { "Could not enumerate $regPathBase : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append } $skippedDevices += $inst } } # Summary "=== Summary ===" | Tee-Object -FilePath $logFile -Append if ($changedDevices.Count -gt 0) { "Devices changed: $($changedDevices.Count)" | Tee-Object -FilePath $logFile -Append $changedDevices | Tee-Object -FilePath $logFile -Append } else { "No devices required updates." | Tee-Object -FilePath $logFile -Append } if ($skippedDevices.Count -gt 0) { "Devices already compliant / skipped: $($skippedDevices.Count)" | Tee-Object -FilePath $logFile -Append $skippedDevices | Tee-Object -FilePath $logFile -Append } if ($errors.Count -gt 0) { "Errors encountered: $($errors.Count)" | Tee-Object -FilePath $logFile -Append $errors | Tee-Object -FilePath $logFile -Append } if (-not $WhatIf) { "If you changed PortName values, run 'pnputil /scan-devices' or reboot so Windows re-registers COM assignments." | Tee-Object -FilePath $logFile -Append } else { "Dry run (WhatIf). No registry writes performed." | Tee-Object -FilePath $logFile -Append } } catch { "Unhandled error: $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append throw
} finally { "Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
} Write-Output "Done. Log saved to: $logFile"
if (-not $WhatIf) { Write-Output "If you wrote values, run: pnputil /scan-devices (or reboot) and then verify COMs and Advanced -> Serial Enumerator in Device Manager." }
- The script will not write a property if it already equals the desired value (this avoids unnecessary registry churn).
- If nothing needed changing you’ll see “No devices required updates.” in the log and a console message.
- If you want stricter behavior (e.g., only set existing serial enumerator properties and do not create explicit names), say “detected-only” and I’ll provide a variant.
- If you want the script to also update HARDWARE\DEVICEMAP\SERIALCOMM entries so the COM shows immediately without reboot/rescan, I can add that — but that’s a more invasive change and can cause immediate conflicts if misused.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #38
Can you only change these values if they do not exist already and reset the pc if the changes happened.
# Run PowerShell as Administrator
# get FTDI ports (adjust filter as needed)
$ports = Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like '*FTDI*' -and $_.InstanceId }
foreach ($p in $ports) {
$inst = $p.InstanceId
Write-Output "Device InstanceId: $inst"
# search Enum for keys that include the InstanceId (case-insensitive)
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$matches = Get-ChildItem -Path $enumBase -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -like "*$($inst.Split('\')[-1])*" -or $_.PSPath -match [regex]::Escape($inst) }
foreach ($m in $matches) {
# possible Device Parameters locations
$dp1 = Join-Path $m.PSPath 'Device Parameters'
$dp2 = Join-Path $m.PSPath '0000\Device Parameters'
foreach ($dp in @($dp1, $dp2)) {
if (Test-Path $dp) {
Write-Output "Updating: $dp"
New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force | Out-Null
}
}
}
}
# rescan devices so Windows sees changes (requires devcon or pnputil)
# devcon rescan <-- if devcon available
pnputil /scan-devices
# Run PowerShell as Administrator
# get FTDI ports (adjust filter as needed)
$ports = Get-PnpDevice -Class Ports | Where-Object { $_.Manufacturer -like '*FTDI*' -and $_.InstanceId }
foreach ($p in $ports) {
$inst = $p.InstanceId
Write-Output "Device InstanceId: $inst"
# search Enum for keys that include the InstanceId (case-insensitive)
$enumBase = 'HKLM:\SYSTEM\CurrentControlSet\Enum'
$matches = Get-ChildItem -Path $enumBase -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -like "*$($inst.Split('\')[-1])*" -or $_.PSPath -match [regex]::Escape($inst) }
foreach ($m in $matches) {
# possible Device Parameters locations
$dp1 = Join-Path $m.PSPath 'Device Parameters'
$dp2 = Join-Path $m.PSPath '0000\Device Parameters'
foreach ($dp in @($dp1, $dp2)) {
if (Test-Path $dp) {
Write-Output "Updating: $dp"
New-ItemProperty -Path $dp -Name 'DeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'DefaultIdleState' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'UserSetDeviceIdleEnabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $dp -Name 'SSIdleTimeout' -PropertyType DWord -Value 0 -Force | Out-Null
}
}
}
}
# rescan devices so Windows sees changes (requires devcon or pnputil)
# devcon rescan <-- if devcon available
pnputil /scan-devices
- Joined
- Mar 14, 2023
- Messages
- 97,513
Below is a ready‑to‑run PowerShell script that does exactly what you asked:
Usage examples:
Notes / Safety
- Finds FTDI COM ports (by Manufacturer or VID_0403).
- For each device it checks the device instance Device Parameters key (handles the common extra 0000 subkey).
- ONLY creates the four selective‑suspend values if they do NOT already exist:
- DeviceIdleEnabled (REG_DWORD)
- DefaultIdleState (REG_DWORD)
- UserSetDeviceIdleEnabled (REG_DWORD)
- SSIdleTimeout (REG_DWORD)
- Also attempts to disable common Serial Enumerator‑style properties if they exist (it will not create new unknown vendor properties).
- Logs actions to a timestamped file on your Desktop.
- If any changes were made (and you did not run the script with -WhatIf), the script will automatically reboot the PC (15 second countdown) so Windows picks up the new values.
Usage examples:
- Dry run (no writes, no reboot):
PowerShell -ExecutionPolicy Bypass -File .\Disable-FTDI-SelectiveSuspend-ApplyIfMissing.ps1 -WhatIf - Apply changes and reboot if changes occurred:
PowerShell -ExecutionPolicy Bypass -File .\Disable-FTDI-SelectiveSuspend-ApplyIfMissing.ps1 - Apply changes but do NOT reboot even if changes happened:
PowerShell -ExecutionPolicy Bypass -File .\Disable-FTDI-SelectiveSuspend-ApplyIfMissing.ps1 -NoReboot
Code:
<#
Disable-FTDI-SelectiveSuspend-ApplyIfMissing.ps1
- Only creates selective-suspend properties if they do not already exist.
- If any changes are made (and not -WhatIf), reboots the PC (15s countdown) so Windows picks up changes. Run as Administrator.
#> param( [switch]$WhatIf, # dry run: show what would change, do not write or reboot [switch]$NoReboot # if specified, do not reboot even when changes were made) # Ensure elevated
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) { Write-Error "Run this script in an elevated PowerShell (Run as Administrator). Exiting." exit 1
} $timestamp = (Get-Date).ToString('yyyyMMdd_HHmmss')
$logFile = Join-Path $env:USERPROFILE "Desktop\FTDI_ApplyIfMissing_$timestamp.log"
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8 # Regex for detecting existing serial-enumerator-like properties (we only touch existing ones)
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)' # Trackers
$changedDevices = @
$skippedDevices = @
$errors = @ # Discover FTDI COM ports
$ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue | Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') } if (-not $ports -or $ports.Count -eq 0) { "No FTDI COM ports found." | Tee-Object -FilePath $logFile -Append Write-Output "No FTDI COM ports found. See log: $logFile" exit 0
} "Found $($ports.Count) FTDI COM port(s)." | Tee-Object -FilePath $logFile -Append foreach ($p in $ports) { "----" | Tee-Object -FilePath $logFile -Append "FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append "InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append $inst = $p.InstanceId if ([string]::IsNullOrWhiteSpace($inst) { "InstanceId empty; skipping." | Tee-Object -FilePath $logFile -Append $skippedDevices += $inst continue } # Build registry paths (safe direct interpolation) $base = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst" $dpCandidates = @( "$base\Device Parameters", "$base\0000\Device Parameters") $foundDP = $false $deviceWasChanged = $false foreach ($dp in $dpCandidates) { "Checking: $dp" | Tee-Object -FilePath $logFile -Append if (Test-Path $dp) { $foundDP = $true "Found Device Parameters: $dp" | Tee-Object -FilePath $logFile -Append # helper: check existence of a named property function Property-Exists { param($Path, $Name) try { $val = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue return ($null -ne $val) } catch { return $false } } # Only create if property does NOT exist $propsToEnsure = @{ 'DeviceIdleEnabled' = 0 'DefaultIdleState' = 0 'UserSetDeviceIdleEnabled' = 0 'SSIdleTimeout' = 0 } foreach ($kv in $propsToEnsure.GetEnumerator { $name = $kv.Key $desired = $kv.Value $exists = Property-Exists -Path $dp -Name $name if ($exists) { "$name already exists at $dp (skipping creation)." | Tee-Object -FilePath $logFile -Append } else { if ($WhatIf) { "WhatIf: would create $name = $desired at $dp" | Tee-Object -FilePath $logFile -Append $deviceWasChanged = $true } else { try { New-Item -Path $dp -Force | Out-Null New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value $desired -Force -ErrorAction Stop | Out-Null "Created $name = $desired at $dp" | Tee-Object -FilePath $logFile -Append $deviceWasChanged = $true } catch { "ERROR creating $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $errors += "ERROR creating $name at $dp : $($_.Exception.Message)" } } } } # Disable any existing Serial Enumerator-like properties (only if they exist) try { $existingProps = (Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue).PSObject.Properties | ForEach-Object { $_.Name } } catch { $existingProps = @ } foreach ($propName in $existingProps) { if ($propName -match $serialEnumRegex) { # read current value try { $curVal = Get-ItemProperty -Path $dp -Name $propName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $propName -ErrorAction SilentlyContinue } catch { $curVal = $null } if ($null -eq $curVal) { $curVal = $null } # treat missing as null if ($null -eq $curVal) { # property exists but no value? create as 0 if ($WhatIf) { "WhatIf: would set existing $propName = 0 at $dp (regex matched)" | Tee-Object -FilePath $logFile -Append $deviceWasChanged = $true } else { try { New-ItemProperty -Path $dp -Name $propName -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null "Set existing $propName = 0 at $dp (regex matched)" | Tee-Object -FilePath $logFile -Append $deviceWasChanged = $true } catch { "ERROR writing $propName at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append $errors += "ERROR writing $propName at $dp : $($_.Exception.Message)" } } } else { # existing numeric value: only change if it does not exist (user requested only create-if-missing), # so we will NOT overwrite. Log it and skip. "$propName exists with value $curVal at $dp (no overwrite per policy)." | Tee-Object -FilePath $logFile -Append } } } break # exit candidates loop for this device } else { "Not found: $dp" | Tee-Object -FilePath $logFile -Append } } if (-not $foundDP) { "Device Parameters not found for instance $inst. Skipping." | Tee-Object -FilePath $logFile -Append $skippedDevices += $inst } else { if ($deviceWasChanged) { $changedDevices += $inst } else { $skippedDevices += $inst } }
} # Summary logging
"=== Summary ===" | Tee-Object -FilePath $logFile -Append
"Devices changed: $($changedDevices.Count)" | Tee-Object -FilePath $logFile -Append
if ($changedDevices.Count -gt 0) { $changedDevices | Tee-Object -FilePath $logFile -Append } "Devices skipped/already-compliant: $($skippedDevices.Count)" | Tee-Object -FilePath $logFile -Append
if ($skippedDevices.Count -gt 0) { $skippedDevices | Tee-Object -FilePath $logFile -Append } if ($errors.Count -gt 0) { "Errors encountered: $($errors.Count)" | Tee-Object -FilePath $logFile -Append $errors | Tee-Object -FilePath $logFile -Append
} # Reboot if changes were made and not a dry run and not suppressed
if (($changedDevices.Count -gt 0) -and (-not $WhatIf) -and (-not $NoReboot) { "Changes applied. Rebooting in 15 seconds to apply changes..." | Tee-Object -FilePath $logFile -Append Write-Output "Changes applied. Rebooting in 15 seconds to apply changes..." Start-Process -FilePath "shutdown.exe" -ArgumentList "/r /t 15 /c `"`"Applying FTDI Device Parameter changes`"`"" -NoNewWindow
} else { if ($changedDevices.Count -eq 0) { "No updates were necessary. No reboot will be performed." | Tee-Object -FilePath $logFile -Append Write-Output "No updates were necessary." } else { "No reboot requested (NoReboot specified or WhatIf). Review log: $logFile" | Tee-Object -FilePath $logFile -Append Write-Output "Changes applied but reboot suppressed. Review log: $logFile" }
} "Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
Write-Output "Done. Log saved to: $logFile"
- This script strictly only CREATES the requested properties when they are missing. It does NOT overwrite existing values (per your request).
- For Serial Enumerator properties we only set existing matching properties to 0; we do NOT create arbitrary unknown vendor property names.
- A reboot is initiated only if changes were actually made and you did not pass -WhatIf and did not pass -NoReboot. Reboot uses shutdown.exe /r /t 15 so you have a 15 second cancel window (if needed).
- Run on one test machine first.
- Change the reboot behavior (immediate Restart-Computer -Force, or longer countdown, or prompt the user), or
- Add an email/console report at the end, or
- Change to overwrite values as well (not just create) if you want to force settings.
sjbesenski
Member
- Joined
- Nov 21, 2025
- Messages
- 71
- Thread Author
-
- #40
Can you edit this code so that the com port number does not show up blank in the advanced settings menu
<#
Disable-FTDI-SelectiveSuspend-ApplyIfMissing.ps1
- Only creates selective-suspend properties if they do not already exist.
- If any changes are made (and not -WhatIf), reboots the PC (15s countdown) so Windows picks up changes.
Run as Administrator.
#>
param(
[switch]$WhatIf, # dry run: show what would change, do not write or reboot
[switch]$NoReboot # if specified, do not reboot even when changes were made
)
# Ensure elevated
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
Write-Error "Run this script in an elevated PowerShell (Run as Administrator). Exiting."
exit 1
}
$timestamp = (Get-Date).ToString('yyyyMMdd_HHmmss')
$logFile = Join-Path $env:USERPROFILE "Desktop\FTDI_ApplyIfMissing_$timestamp.log"
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8
# Regex for detecting existing serial-enumerator-like properties (we only touch existing ones)
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)'
# Trackers
$changedDevices = @()
$skippedDevices = @()
$errors = @()
# Discover FTDI COM ports
$ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue |
Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') }
if (-not $ports -or $ports.Count -eq 0) {
"No FTDI COM ports found." | Tee-Object -FilePath $logFile -Append
Write-Output "No FTDI COM ports found. See log: $logFile"
exit 0
}
"Found $($ports.Count) FTDI COM port(s)." | Tee-Object -FilePath $logFile -Append
foreach ($p in $ports) {
"----" | Tee-Object -FilePath $logFile -Append
"FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append
"InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append
$inst = $p.InstanceId
if ([string]::IsNullOrWhiteSpace($inst)) {
"InstanceId empty; skipping." | Tee-Object -FilePath $logFile -Append
$skippedDevices += $inst
continue
}
$base = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst"
$dpCandidates = @(
"$base\Device Parameters",
"$base\0000\Device Parameters"
)
$foundDP = $false
$deviceWasChanged = $false
foreach ($dp in $dpCandidates) {
"Checking: $dp" | Tee-Object -FilePath $logFile -Append
if (Test-Path $dp) {
$foundDP = $true
"Found Device Parameters: $dp" | Tee-Object -FilePath $logFile -Append
# helper: check existence of a named property
function Property-Exists {
param($Path, $Name)
try {
$val = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
return ($null -ne $val)
} catch {
return $false
}
}
# Only create if property does NOT exist
$propsToEnsure = @{
'DeviceIdleEnabled' = 0
'DefaultIdleState' = 0
'UserSetDeviceIdleEnabled' = 0
'SSIdleTimeout' = 0
}
foreach ($kv in $propsToEnsure.GetEnumerator()) {
$name = $kv.Key
$desired = $kv.Value
$exists = Property-Exists -Path $dp -Name $name
if ($exists) {
"$name already exists at $dp (skipping creation)." | Tee-Object -FilePath $logFile -Append
} else {
if ($WhatIf) {
"WhatIf: would create $name = $desired at $dp" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} else {
try {
New-Item -Path $dp -Force | Out-Null
New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value $desired -Force -ErrorAction Stop | Out-Null
"Created $name = $desired at $dp" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} catch {
"ERROR creating $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append
$errors += "ERROR creating $name at $dp : $($_.Exception.Message)"
}
}
}
}
# Disable any existing Serial Enumerator-like properties (only if they exist)
try {
$existingProps = (Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue).PSObject.Properties | ForEach-Object { $_.Name }
} catch {
$existingProps = @()
}
foreach ($propName in $existingProps) {
if ($propName -match $serialEnumRegex) {
# read current value
try {
$curVal = Get-ItemProperty -Path $dp -Name $propName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $propName -ErrorAction SilentlyContinue
} catch { $curVal = $null }
if ($null -eq $curVal) { $curVal = $null } # treat missing as null
if ($null -eq $curVal) {
# property exists but no value? create as 0
if ($WhatIf) {
"WhatIf: would set existing $propName = 0 at $dp (regex matched)" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} else {
try {
New-ItemProperty -Path $dp -Name $propName -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null
"Set existing $propName = 0 at $dp (regex matched)" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} catch {
"ERROR writing $propName at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append
$errors += "ERROR writing $propName at $dp : $($_.Exception.Message)"
}
}
} else {
# existing numeric value: only change if it does not exist (user requested only create-if-missing),
# so we will NOT overwrite. Log it and skip.
"$propName exists with value $curVal at $dp (no overwrite per policy)." | Tee-Object -FilePath $logFile -Append
}
}
}
break # exit candidates loop for this device
} else {
"Not found: $dp" | Tee-Object -FilePath $logFile -Append
}
}
if (-not $foundDP) {
"Device Parameters not found for instance $inst. Skipping." | Tee-Object -FilePath $logFile -Append
$skippedDevices += $inst
} else {
if ($deviceWasChanged) {
$changedDevices += $inst
} else {
$skippedDevices += $inst
}
}
}
# Summary logging
"=== Summary ===" | Tee-Object -FilePath $logFile -Append
"Devices changed: $($changedDevices.Count)" | Tee-Object -FilePath $logFile -Append
if ($changedDevices.Count -gt 0) { $changedDevices | Tee-Object -FilePath $logFile -Append }
"Devices skipped/already-compliant: $($skippedDevices.Count)" | Tee-Object -FilePath $logFile -Append
if ($skippedDevices.Count -gt 0) { $skippedDevices | Tee-Object -FilePath $logFile -Append }
if ($errors.Count -gt 0) {
"Errors encountered: $($errors.Count)" | Tee-Object -FilePath $logFile -Append
$errors | Tee-Object -FilePath $logFile -Append
}
# Reboot if changes were made and not a dry run and not suppressed
if (($changedDevices.Count -gt 0) -and (-not $WhatIf) -and (-not $NoReboot)) {
"Changes applied. Rebooting in 15 seconds to apply changes..." | Tee-Object -FilePath $logFile -Append
Write-Output "Changes applied. Rebooting in 15 seconds to apply changes..."
Start-Process -FilePath "shutdown.exe" -ArgumentList "/r /t 15 /c `"`"Applying FTDI Device Parameter changes`"`"" -NoNewWindow
} else {
if ($changedDevices.Count -eq 0) {
"No updates were necessary. No reboot will be performed." | Tee-Object -FilePath $logFile -Append
Write-Output "No updates were necessary."
} else {
"No reboot requested (NoReboot specified or WhatIf). Review log: $logFile" | Tee-Object -FilePath $logFile -Append
Write-Output "Changes applied but reboot suppressed. Review log: $logFile"
}
}
"Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
Write-Output "Done. Log saved to: $logFile"
<#
Disable-FTDI-SelectiveSuspend-ApplyIfMissing.ps1
- Only creates selective-suspend properties if they do not already exist.
- If any changes are made (and not -WhatIf), reboots the PC (15s countdown) so Windows picks up changes.
Run as Administrator.
#>
param(
[switch]$WhatIf, # dry run: show what would change, do not write or reboot
[switch]$NoReboot # if specified, do not reboot even when changes were made
)
# Ensure elevated
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
Write-Error "Run this script in an elevated PowerShell (Run as Administrator). Exiting."
exit 1
}
$timestamp = (Get-Date).ToString('yyyyMMdd_HHmmss')
$logFile = Join-Path $env:USERPROFILE "Desktop\FTDI_ApplyIfMissing_$timestamp.log"
"Started: $(Get-Date -Format u)" | Out-File -FilePath $logFile -Encoding UTF8
# Regex for detecting existing serial-enumerator-like properties (we only touch existing ones)
$serialEnumRegex = '(serial.*enum|enum.*serial|serialenumerator|enable.*serial)'
# Trackers
$changedDevices = @()
$skippedDevices = @()
$errors = @()
# Discover FTDI COM ports
$ports = Get-PnpDevice -Class Ports -PresentOnly -ErrorAction SilentlyContinue |
Where-Object { ($_.Manufacturer -and $_.Manufacturer -like '*FTDI*') -or ($_.InstanceId -and $_.InstanceId -match 'VID_0403') }
if (-not $ports -or $ports.Count -eq 0) {
"No FTDI COM ports found." | Tee-Object -FilePath $logFile -Append
Write-Output "No FTDI COM ports found. See log: $logFile"
exit 0
}
"Found $($ports.Count) FTDI COM port(s)." | Tee-Object -FilePath $logFile -Append
foreach ($p in $ports) {
"----" | Tee-Object -FilePath $logFile -Append
"FriendlyName: $($p.FriendlyName)" | Tee-Object -FilePath $logFile -Append
"InstanceId: $($p.InstanceId)" | Tee-Object -FilePath $logFile -Append
$inst = $p.InstanceId
if ([string]::IsNullOrWhiteSpace($inst)) {
"InstanceId empty; skipping." | Tee-Object -FilePath $logFile -Append
$skippedDevices += $inst
continue
}
$base = "HKLM:\SYSTEM\CurrentControlSet\Enum\$inst"
$dpCandidates = @(
"$base\Device Parameters",
"$base\0000\Device Parameters"
)
$foundDP = $false
$deviceWasChanged = $false
foreach ($dp in $dpCandidates) {
"Checking: $dp" | Tee-Object -FilePath $logFile -Append
if (Test-Path $dp) {
$foundDP = $true
"Found Device Parameters: $dp" | Tee-Object -FilePath $logFile -Append
# helper: check existence of a named property
function Property-Exists {
param($Path, $Name)
try {
$val = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
return ($null -ne $val)
} catch {
return $false
}
}
# Only create if property does NOT exist
$propsToEnsure = @{
'DeviceIdleEnabled' = 0
'DefaultIdleState' = 0
'UserSetDeviceIdleEnabled' = 0
'SSIdleTimeout' = 0
}
foreach ($kv in $propsToEnsure.GetEnumerator()) {
$name = $kv.Key
$desired = $kv.Value
$exists = Property-Exists -Path $dp -Name $name
if ($exists) {
"$name already exists at $dp (skipping creation)." | Tee-Object -FilePath $logFile -Append
} else {
if ($WhatIf) {
"WhatIf: would create $name = $desired at $dp" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} else {
try {
New-Item -Path $dp -Force | Out-Null
New-ItemProperty -Path $dp -Name $name -PropertyType DWord -Value $desired -Force -ErrorAction Stop | Out-Null
"Created $name = $desired at $dp" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} catch {
"ERROR creating $name at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append
$errors += "ERROR creating $name at $dp : $($_.Exception.Message)"
}
}
}
}
# Disable any existing Serial Enumerator-like properties (only if they exist)
try {
$existingProps = (Get-ItemProperty -Path $dp -ErrorAction SilentlyContinue).PSObject.Properties | ForEach-Object { $_.Name }
} catch {
$existingProps = @()
}
foreach ($propName in $existingProps) {
if ($propName -match $serialEnumRegex) {
# read current value
try {
$curVal = Get-ItemProperty -Path $dp -Name $propName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $propName -ErrorAction SilentlyContinue
} catch { $curVal = $null }
if ($null -eq $curVal) { $curVal = $null } # treat missing as null
if ($null -eq $curVal) {
# property exists but no value? create as 0
if ($WhatIf) {
"WhatIf: would set existing $propName = 0 at $dp (regex matched)" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} else {
try {
New-ItemProperty -Path $dp -Name $propName -PropertyType DWord -Value 0 -Force -ErrorAction Stop | Out-Null
"Set existing $propName = 0 at $dp (regex matched)" | Tee-Object -FilePath $logFile -Append
$deviceWasChanged = $true
} catch {
"ERROR writing $propName at $dp : $($_.Exception.Message)" | Tee-Object -FilePath $logFile -Append
$errors += "ERROR writing $propName at $dp : $($_.Exception.Message)"
}
}
} else {
# existing numeric value: only change if it does not exist (user requested only create-if-missing),
# so we will NOT overwrite. Log it and skip.
"$propName exists with value $curVal at $dp (no overwrite per policy)." | Tee-Object -FilePath $logFile -Append
}
}
}
break # exit candidates loop for this device
} else {
"Not found: $dp" | Tee-Object -FilePath $logFile -Append
}
}
if (-not $foundDP) {
"Device Parameters not found for instance $inst. Skipping." | Tee-Object -FilePath $logFile -Append
$skippedDevices += $inst
} else {
if ($deviceWasChanged) {
$changedDevices += $inst
} else {
$skippedDevices += $inst
}
}
}
# Summary logging
"=== Summary ===" | Tee-Object -FilePath $logFile -Append
"Devices changed: $($changedDevices.Count)" | Tee-Object -FilePath $logFile -Append
if ($changedDevices.Count -gt 0) { $changedDevices | Tee-Object -FilePath $logFile -Append }
"Devices skipped/already-compliant: $($skippedDevices.Count)" | Tee-Object -FilePath $logFile -Append
if ($skippedDevices.Count -gt 0) { $skippedDevices | Tee-Object -FilePath $logFile -Append }
if ($errors.Count -gt 0) {
"Errors encountered: $($errors.Count)" | Tee-Object -FilePath $logFile -Append
$errors | Tee-Object -FilePath $logFile -Append
}
# Reboot if changes were made and not a dry run and not suppressed
if (($changedDevices.Count -gt 0) -and (-not $WhatIf) -and (-not $NoReboot)) {
"Changes applied. Rebooting in 15 seconds to apply changes..." | Tee-Object -FilePath $logFile -Append
Write-Output "Changes applied. Rebooting in 15 seconds to apply changes..."
Start-Process -FilePath "shutdown.exe" -ArgumentList "/r /t 15 /c `"`"Applying FTDI Device Parameter changes`"`"" -NoNewWindow
} else {
if ($changedDevices.Count -eq 0) {
"No updates were necessary. No reboot will be performed." | Tee-Object -FilePath $logFile -Append
Write-Output "No updates were necessary."
} else {
"No reboot requested (NoReboot specified or WhatIf). Review log: $logFile" | Tee-Object -FilePath $logFile -Append
Write-Output "Changes applied but reboot suppressed. Review log: $logFile"
}
}
"Completed: $(Get-Date -Format u)" | Tee-Object -FilePath $logFile -Append
Write-Output "Done. Log saved to: $logFile"
Similar threads
- Article
- Replies
- 0
- Views
- 19
- Article
- Replies
- 0
- Views
- 32
- Article
- Replies
- 0
- Views
- 113
- Replies
- 0
- Views
- 50
- Article
- Replies
- 0
- Views
- 43