Skip to content

feat: Ensure COM threading apartment for API calls #392

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

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
13f6143
Use associator to find WMI instances for volume APIs
laozc May 31, 2025
983f2a9
Move WMI disk functions to cim package
laozc Jun 20, 2025
7e0ecea
Move WMI Volume and Partition functions to cim package
laozc Jun 20, 2025
dc8f2c7
Move WMI related SMB functions to cim package
laozc Jun 16, 2025
fa17ca6
Use WMI to implement Service related System APIs
laozc May 31, 2025
ccdaad4
Refactor and add test cases
laozc Jun 10, 2025
906c714
Move WMI service functions to cim package
laozc Jun 20, 2025
d4aea08
Move PathValid API to use Win32 API
laozc May 31, 2025
fdf0ed4
Add debug log
laozc Jun 4, 2025
d833da3
Move PathValid function to utils package
laozc Jun 21, 2025
028e9dc
Use WMI to implement iSCSI API to reduce PowerShell overhead
laozc Mar 13, 2025
161c412
Use associator to find iSCSI WMI instances
laozc May 31, 2025
92cafa9
Move iSCSI functions to cim package
laozc Jun 19, 2025
3b82d1c
Move utility functions to utils package
laozc Jun 1, 2025
75f6426
Remove RunPowerShell method from utils
laozc Jun 2, 2025
c474d7e
Ensure IsSymlink works on Windows mount point
laozc Jun 4, 2025
220394e
Ensure COM threading apartment for API calls
laozc Jun 17, 2025
37dd6cf
Ensure COM threading apartment in SMB APIs
laozc Jun 18, 2025
56b4092
Ensure COM threading apartment in Service APIs
laozc Jun 21, 2025
c4aa0fb
Ensure COM threading apartment in iSCSI APIs
laozc Jun 21, 2025
295da7f
Ensure COM threading apartment in Disk APIs
laozc Jun 21, 2025
9a93b7b
Ensure COM threading apartment in Volume APIs
laozc Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,20 @@ func CallMethod(disp *ole.IDispatch, name string, params ...interface{}) (result
}
```

### Association

Association can be used to retrieve all instances that are associated with
a particular source instance.

There are a few Association classes in WMI.

For example, association class [MSFT_PartitionToVolume](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-partitiontovolume)
can be used to retrieve a volume (`MSFT_Volume`) from a partition (`MSFT_Partition`), and vice versa.

```go
collection, err := part.GetAssociated("MSFT_PartitionToVolume", "MSFT_Volume", "Volume", "Partition")
```

<a name="debug-powershell"></a>
## Debug with PowerShell

Expand Down Expand Up @@ -181,6 +195,13 @@ PS C:\Users\Administrator> $vol.FileSystem
NTFS
```

### Association

```powershell
PS C:\Users\Administrator> $partition = (Get-CimInstance -Namespace root\Microsoft\Windows\Storage -ClassName MSFT_Partition -Filter "DiskNumber = 0")[0]
PS C:\Users\Administrator> Get-CimAssociatedInstance -InputObject $partition -Association MSFT_PartitionToVolume
```

### Call Class Method

You may get Class Methods for a single CIM class using `$class.CimClassMethods`.
Expand Down
23 changes: 14 additions & 9 deletions integrationtests/iscsi_ps_scripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ $ProgressPreference = "SilentlyContinue"
$targetName = "%s"

# Get local IPv4 (e.g. 10.30.1.15, not 127.0.0.1)
$address = $(Get-NetIPAddress | Where-Object { $_.InterfaceAlias -eq "Ethernet" -and $_.AddressFamily -eq "IPv4" }).IPAddress
$address = $(Get-NetIPAddress | Where-Object { $_.InterfaceAlias -eq "%s" -and $_.AddressFamily -eq "IPv4" }).IPAddress

# Create virtual disk in RAM
New-IscsiVirtualDisk -Path "ramdisk:scratch-${targetName}.vhdx" -Size 100MB | Out-Null
New-IscsiVirtualDisk -Path "ramdisk:scratch-${targetName}.vhdx" -Size 100MB -ComputerName $env:computername | Out-Null

# Create a target that allows all initiator IQNs and map a disk to the new target
$target = New-IscsiServerTarget -TargetName $targetName -InitiatorIds @("Iqn:*")
Add-IscsiVirtualDiskTargetMapping -TargetName $targetName -DevicePath "ramdisk:scratch-${targetName}.vhdx" | Out-Null
$target = New-IscsiServerTarget -TargetName $targetName -InitiatorIds @("Iqn:*") -ComputerName $env:computername
Add-IscsiVirtualDiskTargetMapping -TargetName $targetName -DevicePath "ramdisk:scratch-${targetName}.vhdx" -ComputerName $env:computername | Out-Null

$output = @{
"iqn" = "$($target.TargetIqn)"
Expand All @@ -68,7 +68,7 @@ $username = "%s"
$password = "%s"
$securestring = ConvertTo-SecureString -String $password -AsPlainText -Force
$chap = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $securestring)
Set-IscsiServerTarget -TargetName $targetName -EnableChap $true -Chap $chap
Set-IscsiServerTarget -TargetName $targetName -EnableChap $true -Chap $chap -ComputerName $env:computername
`

func setChap(targetName string, username string, password string) error {
Expand All @@ -92,7 +92,7 @@ $securestring = ConvertTo-SecureString -String $password -AsPlainText -Force

# Windows initiator does not uses the username for mutual authentication
$chap = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $securestring)
Set-IscsiServerTarget -TargetName $targetName -EnableReverseChap $true -ReverseChap $chap
Set-IscsiServerTarget -TargetName $targetName -EnableReverseChap $true -ReverseChap $chap -ComputerName $env:computername
`

func setReverseChap(targetName string, password string) error {
Expand Down Expand Up @@ -131,8 +131,8 @@ Get-IscsiTarget | Disconnect-IscsiTarget -Confirm:$false
Get-IscsiTargetPortal | Remove-IscsiTargetPortal -confirm:$false

# Clean target
Get-IscsiServerTarget | Remove-IscsiServerTarget
Get-IscsiVirtualDisk | Remove-IscsiVirtualDisk
Get-IscsiServerTarget -ComputerName $env:computername | Remove-IscsiServerTarget
Get-IscsiVirtualDisk -ComputerName $env:computername | Remove-IscsiVirtualDisk

# Stop iSCSI initiator
Get-Service "MsiSCSI" | Stop-Service
Expand Down Expand Up @@ -173,7 +173,12 @@ func runPowershellScript(script string) (string, error) {
}

func setupEnv(targetName string) (*IscsiSetupConfig, error) {
script := fmt.Sprintf(IscsiEnvironmentSetupScript, targetName)
ethernetName := "Ethernet"
if val, ok := os.LookupEnv("ETHERNET_NAME"); ok {
ethernetName = val
}

script := fmt.Sprintf(IscsiEnvironmentSetupScript, targetName, ethernetName)
out, err := runPowershellScript(script)
if err != nil {
return nil, fmt.Errorf("failed setting up environment. err=%v", err)
Expand Down
112 changes: 112 additions & 0 deletions pkg/cim/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ const (
// GPTPartitionTypeMicrosoftReserved is the GUID for Microsoft Reserved Partition (MSR)
// Reserved by Windows for system use
GPTPartitionTypeMicrosoftReserved = "{e3c9e316-0b5c-4db8-817d-f92df00215ae}"

// ErrorCodeCreatePartitionAccessPathAlreadyInUse is the error code (42002) returned when the driver letter failed to assign after partition created
ErrorCodeCreatePartitionAccessPathAlreadyInUse = 42002
)

var (
DiskSelectorListForDiskNumberAndLocation = []string{"Number", "Location"}
DiskSelectorListForPartitionStyle = []string{"PartitionStyle"}
DiskSelectorListForPathAndSerialNumber = []string{"Path", "SerialNumber"}
DiskSelectorListForIsOffline = []string{"IsOffline"}
DiskSelectorListForSize = []string{"Size"}
)

// QueryDiskByNumber retrieves disk information for a specific disk identified by its number.
Expand Down Expand Up @@ -76,3 +87,104 @@ func ListDisks(selectorList []string) ([]*storage.MSFT_Disk, error) {

return disks, nil
}

// InitializeDisk initializes a RAW disk with a particular partition style.
//
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/initialize-msft-disk
// for the WMI method definition.
func InitializeDisk(disk *storage.MSFT_Disk, partitionStyle int) (int, error) {
result, err := disk.InvokeMethodWithReturn("Initialize", int32(partitionStyle))
return int(result), err
}

// RefreshDisk Refreshes the cached disk layout information.
//
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-refresh
// for the WMI method definition.
func RefreshDisk(disk *storage.MSFT_Disk) (int, string, error) {
var status string
result, err := disk.InvokeMethodWithReturn("Refresh", &status)
return int(result), status, err
}

// CreatePartition creates a partition on a disk.
//
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk
// for the WMI method definition.
func CreatePartition(disk *storage.MSFT_Disk, params ...interface{}) (int, error) {
result, err := disk.InvokeMethodWithReturn("CreatePartition", params...)
return int(result), err
}

// SetDiskState takes a disk online or offline.
//
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-online and
// https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-offline
// for the WMI method definition.
func SetDiskState(disk *storage.MSFT_Disk, online bool) (int, string, error) {
method := "Offline"
if online {
method = "Online"
}

var status string
result, err := disk.InvokeMethodWithReturn(method, &status)
return int(result), status, err
}

// RescanDisks rescans all changes by updating the internal cache of software objects (that is, Disks, Partitions, Volumes)
// for the storage setting.
//
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-storagesetting-updatehoststoragecache
// for the WMI method definition.
func RescanDisks() (int, error) {
result, _, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_StorageSetting", "UpdateHostStorageCache", nil)
return result, err
}

// GetDiskNumber returns the number of a disk.
func GetDiskNumber(disk *storage.MSFT_Disk) (uint32, error) {
number, err := disk.GetProperty("Number")
if err != nil {
return 0, err
}
return uint32(number.(int32)), err
}

// GetDiskLocation returns the location of a disk.
func GetDiskLocation(disk *storage.MSFT_Disk) (string, error) {
return disk.GetPropertyLocation()
}

// GetDiskPartitionStyle returns the partition style of a disk.
func GetDiskPartitionStyle(disk *storage.MSFT_Disk) (int32, error) {
retValue, err := disk.GetProperty("PartitionStyle")
if err != nil {
return 0, err
}
return retValue.(int32), err
}

// IsDiskOffline returns whether a disk is offline.
func IsDiskOffline(disk *storage.MSFT_Disk) (bool, error) {
return disk.GetPropertyIsOffline()
}

// GetDiskSize returns the size of a disk.
func GetDiskSize(disk *storage.MSFT_Disk) (int64, error) {
sz, err := disk.GetProperty("Size")
if err != nil {
return -1, err
}
return strconv.ParseInt(sz.(string), 10, 64)
}

// GetDiskPath returns the path of a disk.
func GetDiskPath(disk *storage.MSFT_Disk) (string, error) {
return disk.GetPropertyPath()
}

// GetDiskSerialNumber returns the serial number of a disk.
func GetDiskSerialNumber(disk *storage.MSFT_Disk) (string, error) {
return disk.GetPropertySerialNumber()
}
Loading