New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Microsoft VBScript Runtime error on Windows #1004
Comments
Interesting, thanks for reporting! It seems we have an issue in our VBScript drive detection script: https://github.com/resin-io-modules/drivelist/blob/master/scripts/win32.bat. I'll look at it today and see if I can find the issue. Its unlikely that I'll be able to reproduce, so I might bother you to confirm my fix :) BTW, can you actively reproduce? You can try running the script above directly and see if you can still reproduce, so we discard any issue caused by Electron packaging or any interference between the GUI application and the drive detection script. |
Just to confirm, do you want me to see if I could reproduce the bug by running the |
1 similar comment
Just to confirm, do you want me to see if I could reproduce the bug by running the |
Hi @rakeshdas1 , Yeah, if you could do that, it would be awesome! :) |
Also, just opened up said batch file, and these are the contents. |
So the issue happens on the Public Sub Add(element)
Dictionary.Add element, ""
End Sub This class is used in several places, and Windows doesn't seem to give us a precise stack trace. Can you try running the following (slightly modified) script? I've added some debugging information which should hopefully help us trace down the issue: <!-- : Begin batch script
@echo off
:: Ensure System32 is in the PATH, to avoid weird
:: 'cscript' is not recognized as an internal or external command"" errors.
set PATH=%PATH%;%SYSTEMROOT%\System32
cscript //nologo "%~f0?.wsf"
exit /b
----- Begin wsf script --->
<job><script language="VBScript">
Class List
Private Dictionary
Private Sub Class_Initialize()
Set Dictionary = CreateObject("Scripting.Dictionary")
End Sub
Public Sub Add(element)
Dictionary.Add element, ""
End Sub
Public Function GetArray()
GetArray = Dictionary.Keys()
End Function
Public Function Count
Count = UBound(Dictionary.Keys()) + 1
End Function
End Class
Set WMIService = GetObject("winmgmts:\\.\root\cimv2")
Function BooleanToString(ByVal Value)
If Value Then
BooleanToString = "True"
Else
BooleanToString = "False"
End If
End Function
Function GetOperatingSystemDevice()
Set OperatingSystemsColumn = WMIService.ExecQuery("SELECT SystemDrive FROM Win32_OperatingSystem")
Err.Clear
For Each OperatingSystem in OperatingSystemsColumn
On Error Resume Next
GetOperatingSystemDevice = OperatingSystem.Properties_("SystemDrive")
If Err.Number <> 0 Then
GetOperatingSystemDevice = Nothing
Err.Clear
End If
Next
End Function
Function GetLogicalDisks(ByVal DriveDevice)
Wscript.Echo "Created GetLogicalDisks"
Set GetLogicalDisks = new List
Set DrivePartitionsColumn = WMIService.ExecQuery _
("ASSOCIATORS OF {Win32_DiskDrive.DeviceID=""" & _
DriveDevice & """} WHERE AssocClass = " & _
"Win32_DiskDriveToDiskPartition")
For Each DrivePartition In DrivePartitionsColumn
Set DriveLogicalDisksColumn = WMIService.ExecQuery _
("ASSOCIATORS OF {Win32_DiskPartition.DeviceID=""" & _
DrivePartition.DeviceID & """} WHERE AssocClass = " & _
"Win32_LogicalDiskToPartition")
For Each DriveLogicalDisk In DriveLogicalDisksColumn
Set LogicalDisk = CreateObject("Scripting.Dictionary")
LogicalDisk.Add "Device", DriveLogicalDisk.DeviceID
LogicalDisk.Add "IsProtected", DriveLogicalDisk.Access = 1
GetLogicalDisks.Add(LogicalDisk)
Next
Next
End Function
Function GetTopLevelDrives()
OperatingSystemDevice = GetOperatingSystemDevice()
Wscript.Echo "Created GetTopLevelDrives"
Set GetTopLevelDrives = new List
Set TopLevelDrivesColumn = WMIService.ExecQuery("SELECT * FROM Win32_DiskDrive")
For Each TopLevelDrive In TopLevelDrivesColumn
Set Summary = CreateObject("Scripting.Dictionary")
DeviceID = Replace(TopLevelDrive.DeviceID, "\", "\\")
Summary.Add "Device", DeviceID
Summary.Add "Description", TopLevelDrive.Caption
Summary.Add "Size", TopLevelDrive.Size
Wscript.Echo "Created Mountpoints"
Set Mountpoints = new List
IsRemovable = InStr(TopLevelDrive.MediaType, "Removable") = 1
IsProtected = False
Set LogicalDisks = GetLogicalDisks(DeviceID)
For Each LogicalDisk In LogicalDisks.GetArray()
Mountpoints.Add(LogicalDisk.Item("Device"))
If LogicalDisk.Item("IsProtected") Then
IsProtected = True
End If
If LogicalDisk.Item("Device") = OperatingSystemDevice Then
IsRemovable = False
End If
Next
Summary.Add "Mountpoints", Mountpoints
Summary.Add "IsRemovable", IsRemovable
Summary.Add "IsProtected", IsProtected
GetTopLevelDrives.Add(Summary)
Next
End Function
For Each TopLevelDrive In GetTopLevelDrives().GetArray()
Wscript.Echo "device: """ & TopLevelDrive.Item("Device") & """"
Wscript.Echo "description: """ & TopLevelDrive.Item("Description") & """"
Wscript.Echo "size: " & TopLevelDrive.Item("Size")
Wscript.Echo "raw: """ & TopLevelDrive.Item("Device") & """"
Wscript.Echo "system: " & BooleanToString(Not TopLevelDrive.Item("IsRemovable"))
Wscript.Echo "protected: " & BooleanToString(TopLevelDrive.Item("IsProtected"))
If TopLevelDrive.Item("Mountpoints").Count = 0 Then
Wscript.Echo "mountpoints: []"
Else
Wscript.Echo "mountpoints:"
For Each Mountpoint In TopLevelDrive.Item("Mountpoints").GetArray()
Wscript.Echo " - path: """ & Mountpoint & """"
Next
End If
Wscript.Echo ""
Next
</script></job> |
@jviotti This was the output:
|
Thanks a lot for the help so far @rakeshdas1. That sadly still doesn't say much :/ Can you try the following new edits? <!-- : Begin batch script
@echo off
:: Ensure System32 is in the PATH, to avoid weird
:: 'cscript' is not recognized as an internal or external command"" errors.
set PATH=%PATH%;%SYSTEMROOT%\System32
cscript //nologo "%~f0?.wsf"
exit /b
----- Begin wsf script --->
<job><script language="VBScript">
Class List
Private Dictionary
Private Sub Class_Initialize()
Set Dictionary = CreateObject("Scripting.Dictionary")
End Sub
Public Sub Add(element)
Dictionary.Add element, ""
End Sub
Public Function GetArray()
GetArray = Dictionary.Keys()
End Function
Public Function Count
Count = UBound(Dictionary.Keys()) + 1
End Function
End Class
Set WMIService = GetObject("winmgmts:\\.\root\cimv2")
Function BooleanToString(ByVal Value)
If Value Then
BooleanToString = "True"
Else
BooleanToString = "False"
End If
End Function
Function GetOperatingSystemDevice()
Set OperatingSystemsColumn = WMIService.ExecQuery("SELECT SystemDrive FROM Win32_OperatingSystem")
Err.Clear
For Each OperatingSystem in OperatingSystemsColumn
On Error Resume Next
GetOperatingSystemDevice = OperatingSystem.Properties_("SystemDrive")
If Err.Number <> 0 Then
GetOperatingSystemDevice = Nothing
Err.Clear
End If
Next
End Function
Function GetLogicalDisks(ByVal DriveDevice)
Wscript.Echo "Created GetLogicalDisks"
Set GetLogicalDisks = new List
Set DrivePartitionsColumn = WMIService.ExecQuery _
("ASSOCIATORS OF {Win32_DiskDrive.DeviceID=""" & _
DriveDevice & """} WHERE AssocClass = " & _
"Win32_DiskDriveToDiskPartition")
For Each DrivePartition In DrivePartitionsColumn
Set DriveLogicalDisksColumn = WMIService.ExecQuery _
("ASSOCIATORS OF {Win32_DiskPartition.DeviceID=""" & _
DrivePartition.DeviceID & """} WHERE AssocClass = " & _
"Win32_LogicalDiskToPartition")
For Each DriveLogicalDisk In DriveLogicalDisksColumn
Wscript.Echo "Created LogicalDisk"
Set LogicalDisk = CreateObject("Scripting.Dictionary")
Wscript.Echo "Add Device to LogicalDisk"
LogicalDisk.Add "Device", DriveLogicalDisk.DeviceID
Wscript.Echo "Add IsProtected to LogicalDisk"
LogicalDisk.Add "IsProtected", DriveLogicalDisk.Access = 1
Wscript.Echo "Add LogicalDisk to GetLogicalDisks"
GetLogicalDisks.Add(LogicalDisk)
Next
Next
End Function
Function GetTopLevelDrives()
OperatingSystemDevice = GetOperatingSystemDevice()
Wscript.Echo "Created GetTopLevelDrives"
Set GetTopLevelDrives = new List
Set TopLevelDrivesColumn = WMIService.ExecQuery("SELECT * FROM Win32_DiskDrive")
For Each TopLevelDrive In TopLevelDrivesColumn
Wscript.Echo "Created Summary"
Set Summary = CreateObject("Scripting.Dictionary")
DeviceID = Replace(TopLevelDrive.DeviceID, "\", "\\")
Wscript.Echo "Add Device to Summary"
Summary.Add "Device", DeviceID
Wscript.Echo "Add Description to Summary"
Summary.Add "Description", TopLevelDrive.Caption
Wscript.Echo "Add Size to Summary"
Summary.Add "Size", TopLevelDrive.Size
Wscript.Echo "Created Mountpoints"
Set Mountpoints = new List
IsRemovable = InStr(TopLevelDrive.MediaType, "Removable") = 1
IsProtected = False
Set LogicalDisks = GetLogicalDisks(DeviceID)
For Each LogicalDisk In LogicalDisks.GetArray()
Wscript.Echo "Add LogicalDisk Device to Mountpoints"
Mountpoints.Add(LogicalDisk.Item("Device"))
If LogicalDisk.Item("IsProtected") Then
IsProtected = True
End If
If LogicalDisk.Item("Device") = OperatingSystemDevice Then
IsRemovable = False
End If
Next
Wscript.Echo "Add Mountpoints to Summary"
Wscript.Echo "Add IsRemovable to Summary"
Wscript.Echo "Add IsProtected to Summary"
Summary.Add "Mountpoints", Mountpoints
Summary.Add "IsRemovable", IsRemovable
Summary.Add "IsProtected", IsProtected
Wscript.Echo "Add Summary to GetTopLevelDrives"
GetTopLevelDrives.Add(Summary)
Next
End Function
For Each TopLevelDrive In GetTopLevelDrives().GetArray()
Wscript.Echo "device: """ & TopLevelDrive.Item("Device") & """"
Wscript.Echo "description: """ & TopLevelDrive.Item("Description") & """"
Wscript.Echo "size: " & TopLevelDrive.Item("Size")
Wscript.Echo "raw: """ & TopLevelDrive.Item("Device") & """"
Wscript.Echo "system: " & BooleanToString(Not TopLevelDrive.Item("IsRemovable"))
Wscript.Echo "protected: " & BooleanToString(TopLevelDrive.Item("IsProtected"))
If TopLevelDrive.Item("Mountpoints").Count = 0 Then
Wscript.Echo "mountpoints: []"
Else
Wscript.Echo "mountpoints:"
For Each Mountpoint In TopLevelDrive.Item("Mountpoints").GetArray()
Wscript.Echo " - path: """ & Mountpoint & """"
Next
End If
Wscript.Echo ""
Next
</script></job> I added way more debugging information on that one :) |
This is the output:
|
Thanks a lot again! So it seems that for some reason, a device has a duplicate mount point coming from Here is a new modified script (hopefully the final one). I added extra debugging information to see what is the duplicated letter, and I also fixed the issue, so it should work fine without any errors this time: <!-- : Begin batch script
@echo off
:: Ensure System32 is in the PATH, to avoid weird
:: 'cscript' is not recognized as an internal or external command"" errors.
set PATH=%PATH%;%SYSTEMROOT%\System32
cscript //nologo "%~f0?.wsf"
exit /b
----- Begin wsf script --->
<job><script language="VBScript">
Class List
Private Dictionary
Private Sub Class_Initialize()
Set Dictionary = CreateObject("Scripting.Dictionary")
End Sub
Public Sub Add(element)
Dictionary.Add element, ""
End Sub
Public Function GetArray()
GetArray = Dictionary.Keys()
End Function
Public Function Count
Count = UBound(Dictionary.Keys()) + 1
End Function
' Only works for simple types (e.g: not objects)
Public Function Has(element)
Result = False
For Each Key In Dictionary.Keys()
If Key = Element Then
Result = True
End If
Next
Has = Result
End Function
End Class
Set WMIService = GetObject("winmgmts:\\.\root\cimv2")
Function BooleanToString(ByVal Value)
If Value Then
BooleanToString = "True"
Else
BooleanToString = "False"
End If
End Function
Function GetOperatingSystemDevice()
Set OperatingSystemsColumn = WMIService.ExecQuery("SELECT SystemDrive FROM Win32_OperatingSystem")
Err.Clear
For Each OperatingSystem in OperatingSystemsColumn
On Error Resume Next
GetOperatingSystemDevice = OperatingSystem.Properties_("SystemDrive")
If Err.Number <> 0 Then
GetOperatingSystemDevice = Nothing
Err.Clear
End If
Next
End Function
Function GetLogicalDisks(ByVal DriveDevice)
Set GetLogicalDisks = new List
Set DrivePartitionsColumn = WMIService.ExecQuery _
("ASSOCIATORS OF {Win32_DiskDrive.DeviceID=""" & _
DriveDevice & """} WHERE AssocClass = " & _
"Win32_DiskDriveToDiskPartition")
Wscript.Echo "DriveDevice: " & DriveDevice
For Each DrivePartition In DrivePartitionsColumn
Wscript.Echo "DrivePartition.DeviceID: " & DrivePartition.DeviceID
Set DriveLogicalDisksColumn = WMIService.ExecQuery _
("ASSOCIATORS OF {Win32_DiskPartition.DeviceID=""" & _
DrivePartition.DeviceID & """} WHERE AssocClass = " & _
"Win32_LogicalDiskToPartition")
For Each DriveLogicalDisk In DriveLogicalDisksColumn
Set LogicalDisk = CreateObject("Scripting.Dictionary")
Wscript.Echo "DriveLogicalDisk.DeviceID: " & DriveLogicalDisk.DeviceID
Wscript.Echo "DriveLogicalDisk.Access: " & DriveLogicalDisk.Access
LogicalDisk.Add "Device", DriveLogicalDisk.DeviceID
LogicalDisk.Add "IsProtected", DriveLogicalDisk.Access = 1
GetLogicalDisks.Add(LogicalDisk)
Next
Next
End Function
Function GetTopLevelDrives()
OperatingSystemDevice = GetOperatingSystemDevice()
Set GetTopLevelDrives = new List
Set TopLevelDrivesColumn = WMIService.ExecQuery("SELECT * FROM Win32_DiskDrive")
For Each TopLevelDrive In TopLevelDrivesColumn
Set Summary = CreateObject("Scripting.Dictionary")
DeviceID = Replace(TopLevelDrive.DeviceID, "\", "\\")
Summary.Add "Device", DeviceID
Summary.Add "Description", TopLevelDrive.Caption
Summary.Add "Size", TopLevelDrive.Size
Set Mountpoints = new List
IsRemovable = InStr(TopLevelDrive.MediaType, "Removable") = 1
IsProtected = False
Wscript.Echo "Fetching logical disks"
Set LogicalDisks = GetLogicalDisks(DeviceID)
Wscript.Echo "Create Mountpoints List for " & DeviceID
For Each LogicalDisk In LogicalDisks.GetArray()
Wscript.Echo "Add LogicalDisk Device: " & LogicalDisk.Item("Device")
If Mountpoints.Has(LogicalDisk.Item("Device")) Then
Wscript.Echo "Mountpoint already exists"
Else
Wscript.Echo "Mountpoint does not exist"
Mountpoints.Add(LogicalDisk.Item("Device"))
End If
If LogicalDisk.Item("IsProtected") Then
IsProtected = True
End If
If LogicalDisk.Item("Device") = OperatingSystemDevice Then
IsRemovable = False
End If
Next
Wscript.Echo "Done adding mountpoints"
Wscript.Echo ""
Wscript.Echo ""
Summary.Add "Mountpoints", Mountpoints
Summary.Add "IsRemovable", IsRemovable
Summary.Add "IsProtected", IsProtected
GetTopLevelDrives.Add(Summary)
Next
End Function
For Each TopLevelDrive In GetTopLevelDrives().GetArray()
Wscript.Echo "device: """ & TopLevelDrive.Item("Device") & """"
Wscript.Echo "description: """ & TopLevelDrive.Item("Description") & """"
Wscript.Echo "size: " & TopLevelDrive.Item("Size")
Wscript.Echo "raw: """ & TopLevelDrive.Item("Device") & """"
Wscript.Echo "system: " & BooleanToString(Not TopLevelDrive.Item("IsRemovable"))
Wscript.Echo "protected: " & BooleanToString(TopLevelDrive.Item("IsProtected"))
If TopLevelDrive.Item("Mountpoints").Count = 0 Then
Wscript.Echo "mountpoints: []"
Else
Wscript.Echo "mountpoints:"
For Each Mountpoint In TopLevelDrive.Item("Mountpoints").GetArray()
Wscript.Echo " - path: """ & Mountpoint & """"
Next
End If
Wscript.Echo ""
Next
</script></job> Let me know how that goes, and I'll create a patch with the fix! |
@jviotti, I did recently change drive letters for two of my drives, I think some symlinks were still left in place and that was causing the issue! Maybe the older drive letters were still left in a cache and still thought they led to a drive when they didn't. Anyways, the script did succeed this time and this was the output in case you are curious:
|
Amazing, looks like
Thanks a lot for your continuous help in debugging and getting this issue fixed, its truly appreciated! I'm going to send a PR to fix this issue very soon. |
We've encountered a case where a different partitions of a single drive were reported to have the exact same drive letter. This is obviously weird unexpected behaviour in the first case, which seems to have accidentally caused while manually fiddling with drive letters (maybe a Windows bug?). In any case, the symptom of the issue was the following error: ``` Microsoft VBScript runtime error: This key is already associated with an element of this collection ``` We have our own `List` data structure based on `Scripting.Dictionary`, which will complain with the error above if we try to set a key that already exists. Checking if an element already exists in the list is sadly not an easy venture (mainly when dealing with non-scalar types). As a solution, we've implemented a `Has()` method that only works with scalar types (for now), and use that to decide whether we add a drive letter to the list of mount-points or not. See: balena-io/etcher#1004 (comment) Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
- balena-io-modules/drivelist#130 Change-Type: patch Changelog-Entry: Fix "This key is already associated with an element of this collection" error when multiple partitions point to the same drive letter on Windows. Fixes: #1004 Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
- balena-io-modules/drivelist#130 Change-Type: patch Changelog-Entry: Fix "This key is already associated with an element of this collection" error when multiple partitions point to the same drive letter on Windows. Fixes: #1004 Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
Fantastic, great work @jviotti and @rakeshdas1 ! |
I think I should change my LinkedIn title to "VBScript Developer" by now :P |
Just as a reminder, I did once suggest using a NodeJS WMI module rather than persevering with VBScript ;) |
I am receiving a Microsoft VBScript error when I installed this and ran it on Windows 10, this is the first time that I have encountered this issue. Screenshot of the full error:
The text was updated successfully, but these errors were encountered: