On a recent project, we had a situation where we had to make sure that a specific USB hardware device was always assigned to a COM1. Because of the way USB devices are assigned COM ports, it was never going to get COM1 the first time. I whipped up a quick script to check for the device and then change its COM port. It worked great, but as is the case with many first iterations of scripts, there was a but...
Even though a new COM port was getting assigned, we found is that the previous COM port was remaining marked as used in the ComDB. The ComDB is a binary registry value that keeps track of which COM ports are in use. A great description of how the value works can be found here: http://www.rosseeld.be/DRO/PIC/NextSerialPortNo.htm.
To solve the problem we had two options:
1) Find a way to get access to the msports.dll functions within PowerShell which likely meant digging into the Windows Driver SDK.
2) Parse the binary registry value and update the ComDB manually.
I chose door number two because it seemed much more straight forward. The task was actually a little bit trickier than I expected because the ComDB reverses the COM port bits in each byte (e.g. COM1 is represented by Bit 8 and COM8 is represented by Bit 1). Also, to properly convert the Byte value, each Byte had to have 8 values (leading zeros can't be truncated). To remedy that I used a loop to make sure the binary character array had the proper number of entries.
The function below is the end result...
$DeviceName = "My Hardware Name"
$ComPort = "COM4"
function Change-ComPort {
Param ($Name,$NewPort)
#Queries WMI for Device
$WMIQuery = 'Select * from Win32_PnPEntity where Description = "' + $Name + '"'
$Device = Get-WmiObject -Query $WMIQuery
#Execute only if device is present
if ($Device) {
#Get current device info
$DeviceKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\" + $Device.DeviceID
$PortKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\" + $Device.DeviceID + "\Device Parameters"
$Port = get-itemproperty -path $PortKey -Name PortName
$OldPort = [convert]::ToInt32(($Port.PortName).Replace("COM",""))
#Set new port and update Friendly Name
$FriendlyName = $Name + " (" + $NewPort + ")"
New-ItemProperty -Path $PortKey -Name "PortName" -PropertyType String -Value $NewPort -Force
New-ItemProperty -Path $DeviceKey -Name "FriendlyName" -PropertyType String -Value $FriendlyName -Force
#Release Previous Com Port from ComDB
$Byte = ($OldPort - ($OldPort % 8))/8
$Bit = 8 - ($OldPort % 8)
if ($Bit -eq 8) {
$Bit = 0
$Byte = $Byte - 1
}
$ComDB = get-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\COM Name Arbiter" -Name ComDB
$ComBinaryArray = ([convert]::ToString($ComDB.ComDB[$Byte],2)).ToCharArray()
while ($ComBinaryArray.Length -ne 8) {
$ComBinaryArray = ,"0" + $ComBinaryArray
}
$ComBinaryArray[$Bit] = "0"
$ComBinary = [string]::Join("",$ComBinaryArray)
$ComDB.ComDB[$Byte] = [convert]::ToInt32($ComBinary,2)
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\COM Name Arbiter" -Name ComDB -Value ([byte[]]$ComDB.ComDB)
}
}
Change-ComPort $DeviceName $ComPort
Hey Jeremy, I'm going through the same thing right now and was stoked to see that someone already wrote a script.
ReplyDeleteI am getting an error though. My signature pad is set to COM4 and I need change it to COM1.
I set your vars to:
$DeviceName = "Keyspan USB Serial Port"
$ComPort = "COM4"
When I run the script here is the output. Do you have any advice? Thanks so much in advance!
PS C:\users\squirion\scripts> Change-ComPort $DeviceName $ComPort
PortName : COM1
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\KEYSPAN\*USA19HMAP\00_00\Device Parameters
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\KEYSPAN\*USA19HMAP\00_00
PSChildName : Device Parameters
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
New-ItemProperty : Requested registry access is not allowed.
At line:25 char:1
+ New-ItemProperty -Path $DeviceKey -Name "FriendlyName" -PropertyType String -Val ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (HKEY_LOCAL_MACH...USA19HMAP\00_00:String) [New-ItemProperty], SecurityException
+ FullyQualifiedErrorId : System.Security.SecurityException,Microsoft.PowerShell.Commands.NewItemPropertyCommand
I found out, that you have to run this script with system rights, thanks to this post:
ReplyDeletehttp://stackoverflow.com/questions/6535644/change-port-permanently-using-wmi-and-powershell
I run it as a scheduled task where I can do that, to run a script just to from the powershell prompt I don't know really how, but maybe google can help :)
Thanks Jeremy for the great script!
psexec /s /i powershell
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteInstead of FriendlyName, which might not be very friendly, I prefer to map the COM* to the DeviceID:s
ReplyDelete# Get available COM devices
Add-Log "Scanning COM-ports..." -Level 1
[array]$COM = Get-WMIObject Win32_PnPEntity | where {$_.Name -like "*(COM*"}
foreach ($port in $COM) {$port.Caption = $port.Name.Split("(")[1].Trim(")")} # Borrow Caption for COM-number
$COM = $COM | Sort-Object Caption
$COMlist = New-Object System.Collections.Specialized.OrderedDictionary # Use ordered list instead of messy hash table
foreach ($port in $COM)
{
$COMlist.Add($port.Caption,$port.DeviceID)
Add-Log ($port.Caption + " " + $port.DeviceID) -Level 4
}
And then use i.e. $COMlist["COM2"] with the function.
function Set-ComPort
{
Param (
[string]$DeviceId,
[ValidateScript({$_ -clike "COM*"})]
[string]$ComPort
)
#Queries WMI for Device
$Device = Get-WMIObject Win32_PnPEntity | where {$_.DeviceID -eq $DeviceId}
#Execute only if device is present
if ($Device)
{
#Get current device info
$DeviceKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\" + $Device.DeviceID
$PortKey = "HKLM:\SYSTEM\CurrentControlSet\Enum\" + $Device.DeviceID + "\Device Parameters"
$Port = get-itemproperty -path $PortKey -Name PortName
$OldPort = [convert]::ToInt32(($Port.PortName).Replace("COM",""))
#Set new port and update Friendly Name
$FriendlyName = $device.Name.split("(")[0] + "(" + $ComPort + ")"
New-ItemProperty -Path $PortKey -Name "PortName" -PropertyType String -Value $ComPort -Force
New-ItemProperty -Path $DeviceKey -Name "FriendlyName" -PropertyType String -Value $FriendlyName -Force
#Release Previous Com Port from ComDB
$Byte = ($OldPort - ($OldPort % 8))/8
$Bit = 8 - ($OldPort % 8)
if ($Bit -eq 8)
{
$Bit = 0
$Byte = $Byte - 1
} # end if
$ComDB = get-itemproperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\COM Name Arbiter" -Name ComDB
$ComBinaryArray = ([convert]::ToString($ComDB.ComDB[$Byte],2)).ToCharArray()
while ($ComBinaryArray.Length -ne 8)
{
$ComBinaryArray = ,"0" + $ComBinaryArray
} # end while
$ComBinaryArray[$Bit] = "0"
$ComBinary = [string]::Join("",$ComBinaryArray)
$ComDB.ComDB[$Byte] = [convert]::ToInt32($ComBinary,2)
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\COM Name Arbiter" -Name ComDB -Value ([byte[]]$ComDB.ComDB)
} # end if
} # end function Set-ComPort()
Thus, the usage for changing COM2 to COM8 would be
Set-ComPort -DeviceID $COMlist["COM2"] -ComPort "COM8"
Forgot to remove the line Add-Log. It's an internal function.
Delete* LETS JOIN AND FEEL SENSATION TO PLAY *
ReplyDeleteVideo Adu Ayam
Video Sabung Ayam
Adu Ayam Sampai Mati
Adu Ayam Bangkok
Adu Ayam
Ayam Bangkok
Jadwal Bola Malam Ini
* VISIT OUR SITE AT *
www.bakarayam.co
* ONLY HERE YOU CAN FEEL CONTINUOUS VICTORY *
http://gulaiayammarketing.blogspot.com/2018/10/terungkap-khasiat-daging-kambing-untuk.html
I am getting the following error:
ReplyDeleteget-itemproperty : Cannot find path 'HKLM:\SYSTEM\CurrentControlSet\Enum\USB\VID_0B00&PID_0062\80642982
USBVCOM\VID_0B00&PID_0062\80642982\Device Parameters' because it does not exist.
At C:\Users\jgallas\Desktop\ReassignCOMPort.ps1:18 char:11
+ $Port = get-itemproperty -path $PortKey -Name PortName
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (HKLM:\SYSTEM\Cu...vice Parameters:String) [Get-ItemProperty],
ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyCommand
You cannot call a method on a null-valued expression.
At C:\Users\jgallas\Desktop\ReassignCOMPort.ps1:19 char:3
+ $OldPort = [convert]::ToInt32(($Port.PortName).Replace("COM", ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
New-ItemProperty : Cannot find path 'HKLM:\SYSTEM\CurrentControlSet\Enum\USB\VID_0B00&PID_0062\80642982
USBVCOM\VID_0B00&PID_0062\80642982\Device Parameters' because it does not exist.
At C:\Users\jgallas\Desktop\ReassignCOMPort.ps1:23 char:3
+ New-ItemProperty -Path $PortKey -Name "PortName" -PropertyTyp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (HKLM:\SYSTEM\Cu...vice Parameters:String) [New-ItemProperty],
ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.NewItemPropertyCommand
New-ItemProperty : Cannot find path 'HKLM:\SYSTEM\CurrentControlSet\Enum\USB\VID_0B00&PID_0062\80642982
USBVCOM\VID_0B00&PID_0062\80642982' because it does not exist.
At C:\Users\jgallas\Desktop\ReassignCOMPort.ps1:24 char:3
+ New-ItemProperty -Path $DeviceKey -Name "FriendlyName" -Prope ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (HKLM:\SYSTEM\Cu...D_0062\80642982:String) [New-ItemProperty],
ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.NewItemPropertyCommand
I'm wondering if it's because the device I am trying to modify ports for uses a virtual COM port which then creates a separate registry entry for the device.