Add CIM-based Get-SdnVMCim and Get-SdnVMNetworkAdapterCim for faster VM enumeration - #605
Conversation
Replace slow Hyper-V cmdlets (Get-VM, Get-VMNetworkAdapter) with direct CIM queries against root/virtualization/v2 for significantly faster enumeration on hosts with large numbers of VMs. New functions: - Get-SdnVMCim: CIM-based VM enumeration - Get-SdnVMNetworkAdapterCim: CIM-based adapter enumeration with -All, -ManagementOS, -VMName, -MacAddress support - Get-SdnVMSwitchCim: CIM-based virtual switch enumeration Centralized CIM session management: - New-SdnCimSession/Remove-SdnCimSession in Utilities, mirroring the existing PSRemotingSession pattern Updated callers: - Get-ServerConfigState uses CIM methods - Get/Set-SdnVMNetworkAdapterPortProfile rewritten to use CIM - ComputerName/Credential params added to Get-SdnVMNetworkAdapterPortProfile Closes microsoft#404 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 220ab25a-0e38-4217-8e3c-e0a2e22fd7d9
e7216d8 to
3812f9f
Compare
There was a problem hiding this comment.
Pull request overview
Adds CIM-based Hyper-V enumeration to improve diagnostics performance on hosts with many VMs.
Changes:
- Adds reusable CIM session management.
- Adds CIM-based VM, switch, and network-adapter enumeration.
- Updates server diagnostics and port-profile handling to use CIM.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
src/modules/SdnDiag.Utilities.psm1 |
Adds CIM session lifecycle helpers. |
src/modules/SdnDiag.Server.psm1 |
Integrates CIM enumeration and port-profile operations. |
Suppressed comments (3)
src/modules/SdnDiag.Server.psm1:527
- This replaces four management-OS diagnostic outputs (isolation, team mapping, VLAN, and routing-domain mapping) with only the basic adapter record. Those settings are no longer collected anywhere in this path, so server diagnostics lose information unrelated to the enumeration timeout fix. Preserve equivalent management-OS setting exports.
Get-SdnVMNetworkAdapterCim -ManagementOS | Export-ObjectToFile -FilePath $outDir -Name 'Get-VMNetworkAdapter_ManagementOS' -FileType txt -Format List
src/modules/SdnDiag.Server.psm1:3483
- These properties do not exist on
Msvm_EthernetSwitchPortSecuritySettingData, and Hyper-V switch feature settings must be persisted through the virtual switch management service rather thanSet-CimInstance. Therefore the existing-profile path cannot update a port profile. RetrieveMsvm_EthernetSwitchPortProfileSettingDataand invoke the appropriate feature-modification method, or retainSet-VMSwitchExtensionPortFeature.
$existingSecurity.PortProfileId = $ProfileId.ToString("B")
$existingSecurity.PortProfileData = $ProfileData
$existingSecurity.PortProfileVendorId = $vendorId.ToString("B")
Set-CimInstance -InputObject $existingSecurity -ErrorAction Stop
src/modules/SdnDiag.Server.psm1:3455
- Although
$vmNicwas resolved using both VM name and MAC address, this loop discards that identity and selects the first port allocation with the same MAC. A duplicate static MAC can therefore update another VM's port. Match the allocation through$vmNic.InstanceIDand the allocation'sParentassociation instead of MAC alone.
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
if ($portMac -eq $formattedMac) {
$matchedPort = $port
break
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| $adapter.IsolationSetting | Remove-PropertiesFromObject -PropertiesToRemove 'ParentAdapter','CimSession' | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_IsolationSetting' -FileType txt -Format List | ||
| $adapter.RoutingDomainList | Remove-PropertiesFromObject -PropertiesToRemove 'ParentAdapter','CimSession' | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_RoutingDomainList' -FileType txt -Format List | ||
| $adapter.VlanSetting | Remove-PropertiesFromObject -PropertiesToRemove 'ParentAdapter','CimSession' | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_VlanSetting' -FileType txt -Format List | ||
| $adapter | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_NetworkAdapter' -FileType txt -Format List |
| if ($currentActiveSessions.ComputerName -contains $objectName -and !$Force) { | ||
| $session = ($currentActiveSessions | Where-Object { $_.ComputerName -eq $objectName })[0] |
| "Unable to create CIM session to {0}. Error: {1}" -f $objectName, $_.Exception.Message | Trace-Output -Level:Error | ||
| $_ | Trace-Exception |
| [Parameter(Mandatory = $false)] | ||
| [Microsoft.Management.Infrastructure.CimSession]$CimSession |
| } | ||
| } | ||
|
|
||
| function Get-SdnVMNetworkAdapterCim { |
| $allVmSystems = Get-CimInstance @cimParams -ClassName 'Msvm_ComputerSystem' -Filter "Caption = 'Virtual Machine'" | ||
| foreach ($vm in $allVmSystems) { | ||
| $vmSettings = Get-CimAssociatedInstance -InputObject $vm -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams | | ||
| Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' } |
| } | ||
| } | ||
|
|
||
| function Get-SdnVMCim { |
| # Get port security settings which contain the profile data | ||
| $securitySettings = Get-CimInstance @cimParams -ClassName 'Msvm_EthernetSwitchPortSecuritySettingData' |
| if ($port.Address -and $adapterMac) { | ||
| $portMac = Format-SdnMacAddress -MacAddress $port.Address | ||
| if ($portMac -eq $adapterMac) { | ||
| $matchedPort = $port | ||
| break |
| # Get the associated settings to retrieve additional VM details | ||
| $vmSettings = Get-CimAssociatedInstance -InputObject $vm -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams | | ||
| Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Suppressed comments (13)
src/modules/SdnDiag.Utilities.psm1:1901
- This catch suppresses the terminating session-creation error after tracing it. Callers then receive no session and fail later when they splat a null
CimSession, obscuring the actual connection failure. Forward the caught error as well.
catch {
"Unable to create CIM session to {0}. Error: {1}" -f $objectName, $_.Exception.Message | Trace-Output -Level:Error
$_ | Trace-Exception
}
src/modules/SdnDiag.Server.psm1:2629
- The documented multi-computer path returns a
CimSession[], but this parameter is scalar. Passing twoComputerNamevalues—or forwarding the session array fromGet-SdnVMNetworkAdapterCim—therefore fails parameter conversion before the switch query runs. Accept a session array as the CIM cmdlets do.
[Microsoft.Management.Infrastructure.CimSession]$CimSession
src/modules/SdnDiag.Server.psm1:2689
- This advertised command is not listed in
src/SdnDiagnostics.psd1underFunctionsToExport(the manifest currently exports only the original adapter command). Consequently, users importing SdnDiagnostics cannot callGet-SdnVMNetworkAdapterCim. Add the manifest export and its required offline Pester coverage.
function Get-SdnVMNetworkAdapterCim {
src/modules/SdnDiag.Server.psm1:2807
MacAddressfiltering is only applied in the VM-adapter loops. With-ManagementOS -MacAddress, this branch adds every management adapter, violating the command's filtering contract.
foreach ($adapter in $adapters) {
src/modules/SdnDiag.Server.psm1:2868
- Filtering by
VMNamedisables the emulated-adapter query entirely, so a VM's legacy NICs are omitted even though unfiltered calls include them. Query emulated adapters through the selected VM settings just as is done for synthetic adapters.
# Also get emulated adapters (legacy network adapters) if no VM filter is specified
$emulatedAdapters = @()
if (-not $VMName) {
$emulatedAdapters = Get-CimInstance @cimParams -ClassName 'Msvm_EmulatedEthernetPortSettingData'
}
src/modules/SdnDiag.Server.psm1:2971
- This advertised command is absent from
src/SdnDiagnostics.psd1FunctionsToExport, so it is private after consumers import the module and the new API cannot be invoked. Export it in the manifest and add the corresponding offline Pester tests.
function Get-SdnVMCim {
src/modules/SdnDiag.Server.psm1:3058
Get-ServerConfigStatenow relies on this collection for each VM, but it only includes synthetic NICs. VMs with emulated/legacy NICs therefore lose those adapters from their per-VM diagnostic output. Include both associated adapter classes.
$networkAdapters = @()
if ($vmSettings) {
$networkAdapters = Get-CimAssociatedInstance -InputObject $vmSettings -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
}
src/modules/SdnDiag.Server.psm1:3162
Msvm_EthernetSwitchPortSecuritySettingDatadoes not contain port-profile fields. Those areProfileIdandProfileDataonMsvm_EthernetSwitchPortProfileSettingData. As written, every returned profile has empty values, andRepair-SdnVMNetworkAdapterPortProfilesubsequently fails when parsing the missingProfileId. Query and associate the port-profile feature class instead.
# Get port security settings which contain the profile data
$securitySettings = Get-CimInstance @cimParams -ClassName 'Msvm_EthernetSwitchPortSecuritySettingData'
src/modules/SdnDiag.Server.psm1:3483
- These properties do not exist on
Msvm_EthernetSwitchPortSecuritySettingData, and switch feature settings must be modified throughMsvm_VirtualEthernetSwitchManagementService.ModifyFeatureSettingsrather thanSet-CimInstance. On a port that enters this branch, profile repair fails instead of applying the requested profile. RetrieveMsvm_EthernetSwitchPortProfileSettingData, updateProfileId/ProfileData/VendorId, and invoke the management-service method while handling its return/job status.
$existingSecurity.PortProfileId = $ProfileId.ToString("B")
$existingSecurity.PortProfileData = $ProfileData
$existingSecurity.PortProfileVendorId = $vendorId.ToString("B")
Set-CimInstance -InputObject $existingSecurity -ErrorAction Stop
src/modules/SdnDiag.Server.psm1:512
- Replacing the hydrated adapter with this minimal object also removes the per-adapter ACL, extended ACL, isolation, routing-domain, and VLAN diagnostic files that this collection previously produced. The PR is intended to speed up enumeration, not discard these diagnostics; preserve equivalent targeted CIM collection/exports or retain a fallback for these details.
$adapter | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_NetworkAdapter' -FileType txt -Format List
src/modules/SdnDiag.Server.psm1:527
- This single basic adapter export replaces four management-OS diagnostics: isolation, team mapping, VLAN, and routing-domain mapping. Those settings are not present on the new CIM object, so server collections silently lose operationally important data. Add equivalent CIM-backed exports or preserve targeted Hyper-V calls for these settings.
Get-SdnVMNetworkAdapterCim -ManagementOS | Export-ObjectToFile -FilePath $outDir -Name 'Get-VMNetworkAdapter_ManagementOS' -FileType txt -Format List
src/modules/SdnDiag.Server.psm1:3052
- For a multi-host call,
cimParams.CimSessioncontains every session, so this per-VM association query is sent to all hosts for every VM. Besides producing N×host remote calls, duplicate VM identifiers can associate data from the wrong host. Group enumeration by CIM session and use only the source VM's session for both association queries.
# Get the associated settings to retrieve additional VM details
$vmSettings = Get-CimAssociatedInstance -InputObject $vm -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams |
Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
src/modules/SdnDiag.Server.psm1:3132
- The exported
Get-SdnVMNetworkAdapterPortProfilesignature now adds remote connectivity parameters, buttests/offline/Server.Tests.ps1only mocks this command while testingRepair-SdnVMNetworkAdapterPortProfile; it has no tests for this function or the new parameters. Add offline tests for local,ComputerName, and credential/session behavior.
[Parameter(Mandatory = $false)]
[System.String[]]$ComputerName,
[Parameter(Mandatory = $false)]
[System.Management.Automation.PSCredential]
| if ($VMName -and $vmCim) { | ||
| $vmSettingData = Get-CimAssociatedInstance -InputObject $vmCim -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams | | ||
| Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' } |
- Add Get-SdnCimAssociatedInstance wrapper for testability (Get-CimAssociatedInstance requires [CimInstance] typed InputObject which prevents Pester mock interception) - Add 12 tests for Get-SdnVMSwitchCim, Get-SdnVMNetworkAdapterCim, Get-SdnVMCim, and Get-SdnVMNetworkAdapterPortProfile in Server.Tests.ps1 - Add 7 tests for New-SdnCimSession and Remove-SdnCimSession in Utilities.Tests.ps1 - Fix Remove-SdnCimSession to use -Id parameter instead of pipeline for mockability - Use broad mock pattern with switch routing for Get-CimInstance (ParameterFilter does not reliably intercept splatted parameters) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 220ab25a-0e38-4217-8e3c-e0a2e22fd7d9
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Suppressed comments (11)
src/modules/SdnDiag.Server.psm1:3196
- This queries the port security class, but that class does not expose port-profile fields.
ProfileId,ProfileData, andVendorIdbelong toMsvm_EthernetSwitchPortProfileSettingData(and are not namedPortProfileId/PortProfileData), so returned profile objects remain empty and callers such as the repair path can fail when parsingProfileId. Query and associate the port-profile setting class instead.
# Get port security settings which contain the profile data
$securitySettings = Get-CimInstance @cimParams -ClassName 'Msvm_EthernetSwitchPortSecuritySettingData'
src/modules/SdnDiag.Server.psm1:3517
- Existing profiles cannot be updated this way. The queried security object has no
PortProfileId,PortProfileData, orPortProfileVendorIdproperties, and Hyper-V feature settings must be committed throughMsvm_VirtualEthernetSwitchManagementService.ModifyFeatureSettings, rather thanSet-CimInstance. Use the associatedMsvm_EthernetSwitchPortProfileSettingDataobject (ProfileId,ProfileData,VendorId) and invoke the management-service method.
# Update existing security settings via CIM
$existingSecurity.PortProfileId = $ProfileId.ToString("B")
$existingSecurity.PortProfileData = $ProfileData
$existingSecurity.PortProfileVendorId = $vendorId.ToString("B")
Set-CimInstance -InputObject $existingSecurity -ErrorAction Stop
src/modules/SdnDiag.Server.psm1:512
- This replacement silently drops the per-adapter ACL, extended ACL, isolation, routing-domain, and VLAN diagnostic files that the previous block exported. The new CIM adapter object contains none of those settings, so
Get-SdnConfigState -Role Serverloses data needed to diagnose VM networking. Preserve those exports or add CIM equivalents rather than exporting only the basic adapter.
$prefix = (Format-SdnMacAddress -MacAddress $adapter.MacAddress)
$adapter | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_NetworkAdapter' -FileType txt -Format List
src/modules/SdnDiag.Server.psm1:527
- Replacing the four management-OS queries with this basic adapter export removes isolation, team mapping, VLAN, and routing-domain configuration from the diagnostics archive. Those settings are not present on the new CIM result, so this is a functional data-collection regression. Retain the existing detail queries or implement equivalent CIM collection.
Get-SdnVMNetworkAdapterCim -ManagementOS | Export-ObjectToFile -FilePath $outDir -Name 'Get-VMNetworkAdapter_ManagementOS' -FileType txt -Format List
src/modules/SdnDiag.Server.psm1:2723
Get-SdnVMNetworkAdapterCimis presented as a new callable function, but it was not added tosrc/SdnDiagnostics.psd1FunctionsToExport. After importing SdnDiagnostics, consumers cannot invoke it; only internal module code andInModuleScopetests can. Export the function in the manifest.
function Get-SdnVMNetworkAdapterCim {
src/modules/SdnDiag.Server.psm1:3005
Get-SdnVMCimis not listed insrc/SdnDiagnostics.psd1FunctionsToExport, despite being introduced as a user-facing function. It therefore is unavailable after a normal module import. Add it to the manifest export list.
function Get-SdnVMCim {
src/modules/SdnDiag.Server.psm1:3080
- The documented state values are mapped to the wrong names here: 32768 is Paused, 32769 is Suspended/Saved, 32770 is Starting, 32771 is Snapshotting, 32773 is Saving, 32774 is Stopping, 32776 is Pausing, and 32777 is Resuming. As written, diagnostics report incorrect VM state for every vendor-specific transition.
32768 { 'Starting' }
32769 { 'Saving' }
32770 { 'Stopping' }
32771 { 'Pausing' }
32773 { 'Resuming' }
src/modules/SdnDiag.Server.psm1:2902
- When
-VMNameis used, emulated (legacy) adapters are never queried, so a VM with a legacy NIC returns an incomplete adapter list even though this function promises synthetic and emulated adapters. RetrieveMsvm_EmulatedEthernetPortSettingDatafrom the selected VM settings just as is done for synthetic adapters.
# Also get emulated adapters (legacy network adapters) if no VM filter is specified
$emulatedAdapters = @()
if (-not $VMName) {
$emulatedAdapters = Get-CimInstance @cimParams -ClassName 'Msvm_EmulatedEthernetPortSettingData'
}
src/modules/SdnDiag.Server.psm1:2861
- The
MacAddressfilter is only applied in the VM-adapter loops. With-ManagementOS -MacAddress, every host adapter is returned, which violates the function's filtering contract and forced the setter to filter again manually. Apply the same normalized comparison before adding a management adapter.
$adapterObject = [PSCustomObject]@{
Name = $adapter.Name
MacAddress = $adapter.PermanentAddress
SwitchName = $resolvedSwitch
src/modules/SdnDiag.Utilities.psm1:1901
- A failed session creation is only traced and then swallowed, so callers continue with an empty/null session and fail later with a misleading CIM binding/query error. Follow the module's catch pattern by also emitting the original error so
-ErrorAction Stopworks for callers.
catch {
"Unable to create CIM session to {0}. Error: {1}" -f $objectName, $_.Exception.Message | Trace-Output -Level:Error
$_ | Trace-Exception
}
tests/offline/Server.Tests.ps1:724
- This mock assigns port-profile properties to
Msvm_EthernetSwitchPortSecuritySettingData, but real instances of that class do not have them, so the test validates an object shape Hyper-V can never return and masks the production class/property bug. ModelMsvm_EthernetSwitchPortProfileSettingDatawithProfileId,ProfileData, andVendorId, then assert the real association path.
'Msvm_EthernetSwitchPortSecuritySettingData' {
return @(
[PSCustomObject]@{
InstanceID = 'Microsoft:AAAAAAAA-1111-2222-3333-444444444444\BBBBBBBB-5555-6666-7777-888888888888\port001/security'
PortProfileId = '{11111111-2222-3333-4444-555555555555}'
| [Parameter(Mandatory = $false)] | ||
| [string]$ResultClassName, | ||
|
|
||
| [Parameter(Mandatory = $false)] | ||
| [string]$Namespace, | ||
|
|
||
| [Parameter(Mandatory = $false)] | ||
| [string]$ErrorAction | ||
| ) | ||
|
|
||
| $params = @{ | ||
| InputObject = $InputObject | ||
| } | ||
| if ($ResultClassName) { $params['ResultClassName'] = $ResultClassName } | ||
| if ($Namespace) { $params['Namespace'] = $Namespace } | ||
|
|
||
| return (Get-CimAssociatedInstance @params) |
- Fix port profile class: SecuritySettingData -> ProfileSettingData - Fix VM state mapping (32768=Paused, 32769=Saved, etc.) - Restore ConfigState diagnostic exports (ACL, VLAN, Isolation, etc.) - Fix N+1 round-trips: bulk-query VirtualSystemSettingData for vmLookup - Add MacAddress filter on ManagementOS path - Include emulated adapters when filtering by VMName - Add CimSession forwarding to Get-SdnCimAssociatedInstance wrapper - Accept CimSession array in Get-SdnVMSwitchCim - Add Get-SdnVMCim and Get-SdnVMNetworkAdapterCim to manifest exports - Add Write-Error to New-SdnCimSession catch block - Update tests for corrected CIM class and bulk query approach Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 220ab25a-0e38-4217-8e3c-e0a2e22fd7d9
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Suppressed comments (16)
src/modules/SdnDiag.Server.psm1:521
- This reintroduces
Get-VMNetworkAdapteronce per VM. On the large deployments targeted by #404, that creates an N-call Hyper-V enumeration path and can still hit the same timeout the CIM migration is intended to remove. Gather the required detail in one bulk operation or query the corresponding CIM feature classes instead.
$hvAdapters = Get-VMNetworkAdapter -VMName $vm.Name -ErrorAction SilentlyContinue
src/modules/SdnDiag.Server.psm1:2914
VMNameis interpolated unescaped into WQL. A VM name containing an apostrophe makes this filter invalid, so the exported API fails for a valid Hyper-V name. Escape the value for WQL before constructing the filter or use a non-interpolated lookup.
$vmFilter = "ElementName = '$VMName' AND Caption = 'Virtual Machine'"
src/modules/SdnDiag.Server.psm1:3112
VMNameis appended to the WQL filter without escaping. A valid VM name containing an apostrophe makes the query malformed, soGet-SdnVMCim -VMNamefails for that VM. Escape the value for WQL before interpolation or use a non-interpolated lookup.
$filter += " AND ElementName = '$VMName'"
src/modules/SdnDiag.Server.psm1:3138
- This association lookup runs once per VM, followed by another per-VM adapter lookup below. That makes
Get-SdnVMCimperform O(VM count) remote round trips, which scales poorly for the high-VM-count hosts this function is intended to accelerate. Bulk-query settings and adapters once, then join them by VM ID in memory as the adapter function already does.
$vmSettings = Get-SdnCimAssociatedInstance -InputObject $vm -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams |
Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
src/modules/SdnDiag.Server.psm1:3143
- Only synthetic adapters are associated here.
Get-SdnVMNetworkAdapterCimexplicitly supportsMsvm_EmulatedEthernetPortSettingData, but VMs with legacy adapters will lose those adapters fromGet-SdnVMCimand from the per-VM diagnostic export that now consumes it. Include both adapter classes.
$networkAdapters = Get-SdnCimAssociatedInstance -InputObject $vmSettings -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
src/modules/SdnDiag.Server.psm1:3254
- This assumes a profile
InstanceIDis the port allocation ID plus a slash suffix, but CIMInstanceIDis an opaque key and Hyper-V definesMsvm_EthernetPortSettingDataComponentto associate a port allocation with its feature settings. On real hosts this derived key can miss the profile and return emptyProfileId/ProfileData; resolve profiles through the association instead of parsing IDs.
foreach ($profile in $profileSettings) {
$portPath = $profile.InstanceID -replace '/[^/]+$', ''
$profileLookup[$portPath] = $profile
src/modules/SdnDiag.Server.psm1:3541
- The selected
$vmNicis not used to identify the port; this loop chooses the first allocation with the same MAC address. Duplicate MACs can exist during misconfiguration—the exact scenario repair tooling must handle—so this can update another VM's profile. Correlate the allocation to$vmNic.InstanceID/Parent(or the CIM association) and use MAC only as an additional check.
foreach ($port in $portAllocations) {
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
if ($portMac -eq $formattedMac) {
$matchedPort = $port
break
src/modules/SdnDiag.Server.psm1:3556
- This repeats the unsupported assumption that a profile
InstanceIDis derived from the port allocation ID by removing a slash suffix. Hyper-V exposes the port-to-feature relationship throughMsvm_EthernetPortSettingDataComponent; if the opaque IDs do not share this shape,$existingProfileremains null and the code attempts to add a duplicate profile instead of updating the existing one. Resolve the profile through the CIM association.
foreach ($profile in $profileSettings) {
$profilePortPath = $profile.InstanceID -replace '/[^/]+$', ''
if ($profilePortPath -eq $portInstancePath) {
src/modules/SdnDiag.Server.psm1:3290
- Port allocations are selected solely by MAC address. Duplicate MACs—especially across the supported
ComputerName[]targets—cause an adapter to inherit the first matching port's profile and port name from another VM or host. Match the allocation through the adapter'sInstanceID/Parentrelationship and then traverse the port-feature association.
foreach ($port in $portAllocations) {
if ($port.Address -and $adapterMac) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
if ($portMac -eq $adapterMac) {
$matchedPort = $port
break
src/modules/SdnDiag.Server.psm1:2928
- With multiple
ComputerNamevalues, this filtered query can return one VM object per host, but the wrapper forwards$vmCimas a singleInputObjectto a cmdlet that accepts oneCimInstance. The advertised multi-host-VMNamepath therefore fails when that VM name exists on more than one target. Enumerate the VM instances and resolve each association in its originating session.
$vmSettingData = Get-SdnCimAssociatedInstance -InputObject $vmCim -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams |
Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
src/modules/SdnDiag.Server.psm1:2641
ComputerNameis documented and typed as an array in the new public callers, so they can splat multiple sessions here, but this wrapper only accepts oneCimSession. Multi-host calls toGet-SdnVMCimor VM-filtered adapter queries will fail parameter conversion before reachingGet-CimAssociatedInstance, whose native parameter supports an array.
This issue also appears in the following locations of the same file:
- line 3143
- line 3252
- line 3554
[Microsoft.Management.Infrastructure.CimSession]$CimSession
src/modules/SdnDiag.Server.psm1:2932
- When the VM exists but no realized setting is returned (for example during a transitional or inconsistent state),
$adaptersis left as the earlier host-wide synthetic-adapter query. The-VMNamecall then returns adapters from every VM instead of none. Clear the collection when the association cannot be resolved.
if ($vmSettingData) {
$vmPath = $vmSettingData.CimSystemProperties.CimInstance
$adapters = Get-SdnCimAssociatedInstance -InputObject $vmSettingData -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
}
src/modules/SdnDiag.Server.psm1:2736
- These property names misstate the CIM values:
IOVPreferredis an SR-IOV preference flag, not a switch type, andMaxIOVOffloadsis an offload count, not a bandwidth percentage. Consumers of this new public API will receive incorrectly labeled data; expose the source semantics or derive the advertised values correctly.
SwitchType = $sw.IOVPreferred
Notes = $sw.Notes
BandwidthPercentage = $sw.MaxIOVOffloads
src/modules/SdnDiag.Server.psm1:3260
- The existing
Get-SdnVMNetworkAdapterPortProfile -Allcontract means all VM adapters, while the new helper's-Allexplicitly adds Management OS adapters too. Passing the switch through silently broadens this exported API and can return host-vNIC profiles to callers that requested VM adapters. Leave the helper at its default VM-only behavior for this parameter set.
if ($All) { $adapterParams.Add('All', $true) }
src/modules/SdnDiag.Server.psm1:3569
- This PR substantially rewrites the exported setter's existing-profile update and fallback creation paths, but no corresponding Pester tests cover
Set-SdnVMNetworkAdapterPortProfile. Please add offline tests for both branches, including verification that the intended adapter/port is selected, following the existing Server test patterns.
Set-CimInstance -InputObject $existingProfile -ErrorAction Stop
src/modules/SdnDiag.Utilities.psm1:1944
- A failed individual removal is only logged and then treated as success. Callers cannot detect that the named session remains, and this catch also bypasses the module's normal exception tracing/error stream behavior. Trace and emit the caught error after the warning.
catch {
"Unable to remove CIM session {0} for {1}. Error: {2}" -f $session.Name, $session.ComputerName, $_.Exception.Message | Trace-Output -Level:Warning
}
| try { | ||
| $filter = $null | ||
| if ($Name) { | ||
| $filter = "ElementName = '$Name'" |
- Add Get-SdnVMNetworkAdapterVlanCim, IsolationCim, ExtendedAclCim, RoutingDomainCim - Update Get-ServerConfigState and Repair-SdnVMNetworkAdapterPortProfile to use CIM VLAN reads - Add Pester tests for all 4 new port setting functions (79 total, 0 failures) - Fix Repair tests to match CIM-based VLAN read path Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 220ab25a-0e38-4217-8e3c-e0a2e22fd7d9
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Suppressed comments (10)
src/modules/SdnDiag.Server.psm1:3139
- This performs an association query separately for every VM, and the following adapter lookup performs another per-VM association query. A host with N VMs therefore incurs roughly 2N CIM round trips, undermining the bulk-enumeration performance goal and retaining the timeout risk from #404. Query realized settings and adapters in bulk and join them by VM ID.
# Get the associated settings to retrieve additional VM details
$vmSettings = Get-SdnCimAssociatedInstance -InputObject $vm -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams |
Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
src/modules/SdnDiag.Server.psm1:3390
- This forward-slash parsing does not identify the parent port for real Hyper-V feature
InstanceIDvalues. The relationship is represented byMsvm_EthernetPortSettingDataComponent; because the resulting key does not equal the allocationInstanceID, all adapters are emitted with null isolation settings. Join through the association instead of parsing the vendor-specific ID.
# Build lookup: port InstanceID -> isolation setting
$isolationByPort = @{}
foreach ($iso in $isolationSettings) {
$portPath = $iso.InstanceID -replace '/[^/]+$', ''
$isolationByPort[$portPath] = $iso
src/modules/SdnDiag.Server.psm1:3519
- Routing-domain feature IDs are not
<port>/<suffix>paths; Hyper-V associates each feature with its port throughMsvm_EthernetPortSettingDataComponent. This replacement therefore leaves the full feature ID as the lookup key, which never equals$port.InstanceID, so routing-domain entries are silently omitted. Use the association to construct this lookup.
# Build lookup: port InstanceID -> list of routing domain settings
$routingByPort = @{}
foreach ($rd in $routingSettings) {
$portPath = $rd.InstanceID -replace '/[^/]+$', ''
if (-not $routingByPort.ContainsKey($portPath)) {
src/modules/SdnDiag.Server.psm1:3652
- The VLAN feature is associated with its allocation through
Msvm_EthernetPortSettingDataComponent; itsInstanceIDis not a forward-slash child path. This key therefore never matches$port.InstanceID, causing every configured adapter to be reported asUntagged. That also makesRepair-SdnVMNetworkAdapterPortProfilemiss conflicting VLANs and skip their removal. Build the lookup from the association.
# Build lookup: port InstanceID -> VLAN setting
$vlanByPort = @{}
foreach ($vlan in $vlanSettings) {
$portPath = $vlan.InstanceID -replace '/[^/]+$', ''
$vlanByPort[$portPath] = $vlan
src/modules/SdnDiag.Server.psm1:4107
- The existing-profile lookup repeats the invalid
<port>/<suffix>assumption. Real Hyper-V port features are linked byMsvm_EthernetPortSettingDataComponent, so an existing profile is not found and this function falls into the add path, which can fail because the adapter already has that feature. Resolve the profile through the association (or a verified parent relation) before deciding to add it.
$portInstancePath = $matchedPort.InstanceID
foreach ($profile in $profileSettings) {
$profilePortPath = $profile.InstanceID -replace '/[^/]+$', ''
if ($profilePortPath -eq $portInstancePath) {
$existingProfile = $profile
src/modules/SdnDiag.Server.psm1:2737
- These fields do not represent what their names claim:
IOVPreferredis a Boolean SR-IOV preference, not the Hyper-V switch type (External/Internal/Private), andMaxIOVOffloadsis not a bandwidth percentage. Because this function is exported as aGet-VMSwitchalternative, consumers receive materially incorrect switch metadata. Derive the actual switch type/bandwidth data or expose these values under accurate property names.
SwitchType = $sw.IOVPreferred
Notes = $sw.Notes
BandwidthPercentage = $sw.MaxIOVOffloads
src/modules/SdnDiag.Server.psm1:523
- The PR says this caller moves isolation, VLAN, and routing-domain exports to CIM and leaves only TeamMapping on Hyper-V, but this new per-VM loop still invokes the timeout-prone
Get-VMNetworkAdapterand reads those Hyper-V properties. On dense hosts this reintroduces one slow enumeration per VM—the scaling problem in #404. Bulk-query the new CIM setting functions and join their results instead.
# collect per-VM adapter diagnostic details using Hyper-V cmdlets
try {
$hvAdapters = Get-VMNetworkAdapter -VMName $vm.Name -ErrorAction SilentlyContinue
foreach ($hvAdapter in $hvAdapters) {
$prefix = (Format-SdnMacAddress -MacAddress $hvAdapter.MacAddress)
src/modules/SdnDiag.Server.psm1:3145
Get-SdnVMCimpopulatesNetworkAdaptersfrom synthetic adapters only, so VMs with legacy/emulated NICs lose those adapters compared withGet-VM.Get-SdnVMNetworkAdapterCimexplicitly handlesMsvm_EmulatedEthernetPortSettingData; the VM result needs to include the same class, ideally as part of the bulk join.
# Get network adapters associated with this VM
$networkAdapters = @()
if ($vmSettings) {
$networkAdapters = Get-SdnCimAssociatedInstance -InputObject $vmSettings -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
}
src/modules/SdnDiag.Server.psm1:3709
- Unlike
OperationMode,PvlanModeis returned as its raw CIM integer.Repair-SdnVMNetworkAdapterPortProfilecompares this property with'Promiscuous', so it takes the wrong detail path for promiscuous PVLANs, and this exported replacement is inconsistent withGet-VMNetworkAdapterVlan. Map values 1/2/3 toIsolated/Community/Promiscuous.
PrivateVlanMode = if ($vlan) { $vlan.PvlanMode } else { $null }
src/modules/SdnDiag.Server.psm1:4123
- This exported function now has new CIM update and add/fallback behavior, but
Server.Tests.ps1adds no tests for either path. The direct property mutation and profile-parent matching regressions would both have been caught by representative CIM-instance tests. Please add offline Pester coverage for updating an existing profile and adding a missing profile.
Set-CimInstance -InputObject $existingProfile -ErrorAction Stop
}
else {
# Create new port profile settings
# Fall back to Hyper-V cmdlet for creating new port profiles as CIM creation requires complex WMI method invocation
| [Parameter(Mandatory = $true, ParameterSetName = 'Management')] | ||
| [switch]$ManagementOS | ||
| [Parameter(Mandatory = $false)] | ||
| [Microsoft.Management.Infrastructure.CimSession]$CimSession |
| # Build lookup: port InstanceID -> list of ACL settings | ||
| $aclByPort = @{} | ||
| foreach ($acl in $aclSettings) { | ||
| $portPath = $acl.InstanceID -replace '/[^/]+$', '' | ||
| if (-not $aclByPort.ContainsKey($portPath)) { |
| $existingProfile.ProfileId = $ProfileId.ToString("B") | ||
| $existingProfile.ProfileData = $ProfileData | ||
| $existingProfile.VendorId = $vendorId.ToString("B") | ||
| Set-CimInstance -InputObject $existingProfile -ErrorAction Stop |
…ctions Drop legacy AclList export. Use Get-SdnVMNetworkAdapterExtendedAclCim, IsolationCim, RoutingDomainCim, and VlanCim filtered by VMName instead of the slow Get-VMNetworkAdapter per-VM call. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 220ab25a-0e38-4217-8e3c-e0a2e22fd7d9
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Suppressed comments (12)
src/modules/SdnDiag.Server.psm1:521
- These four helpers are invoked inside the per-VM loop, but each helper bulk-queries every port/allocation on the host and then calls
Get-SdnVMNetworkAdapterCim, which performs additional host-wide queries. On the large deployments targeted by #404, this changes collection into repeated full-host scans and can recreate the timeout problem. Query each setting class once before this loop, then group/filter the cached results by VM/MAC while writing each VM directory.
Get-SdnVMNetworkAdapterExtendedAclCim -VMName $vm.Name |
src/modules/SdnDiag.Server.psm1:521
- The previous collection exported each adapter's basic
AclListasGet-VM_AclList, but the replacement only collects extended ACLs, isolation, routing-domain, and VLAN data. Basic port ACL diagnostics are now silently absent from every VM bundle. Retain the existing ACL export or add the corresponding CIM query/join.
# collect per-VM adapter diagnostic details using CIM
try {
Get-SdnVMNetworkAdapterExtendedAclCim -VMName $vm.Name |
src/modules/SdnDiag.Server.psm1:3255
- Hyper-V port-feature InstanceIDs use a
\C\...suffix appended to the port-allocation InstanceID, not a/featuresuffix. This expression leaves the real ACL InstanceID unchanged, so it never matches the allocation ID and configured ACLs are returned as empty. Strip the CIM feature suffix when building the lookup.
$portPath = $acl.InstanceID -replace '/[^/]+$', ''
src/modules/SdnDiag.Server.psm1:3395
- Hyper-V port-feature InstanceIDs use a
\C\...suffix appended to the port-allocation InstanceID, not a/featuresuffix. This expression leaves the real isolation-setting InstanceID unchanged, so every adapter is emitted with null isolation fields. Strip the CIM feature suffix when building the lookup.
$portPath = $iso.InstanceID -replace '/[^/]+$', ''
src/modules/SdnDiag.Server.psm1:3524
- Hyper-V port-feature InstanceIDs use a
\C\...suffix appended to the port-allocation InstanceID, not a/featuresuffix. This expression leaves the real routing-setting InstanceID unchanged, so it never matches an allocation and routing-domain results are empty. Strip the CIM feature suffix when building the lookup.
$portPath = $rd.InstanceID -replace '/[^/]+$', ''
src/modules/SdnDiag.Server.psm1:3657
- Hyper-V port-feature InstanceIDs use a
\C\...suffix appended to the port-allocation InstanceID, not a/featuresuffix. This expression leaves the real VLAN-setting InstanceID unchanged; the lookup then misses configured VLANs and reports them asUntagged, which preventsRepair-SdnVMNetworkAdapterPortProfilefrom removing conflicting VLAN configuration. Strip the CIM feature suffix when building the lookup.
$portPath = $vlan.InstanceID -replace '/[^/]+$', ''
src/modules/SdnDiag.Server.psm1:4111
- This repeats the
/profileparsing assumption, but real Hyper-V profile feature IDs append a\C\...suffix to the allocation ID. Existing profiles are therefore treated as absent and the code falls into the add path, which can fail because the feature already exists. Normalize the feature ID to its allocation prefix before comparing.
$profilePortPath = $profile.InstanceID -replace '/[^/]+$', ''
src/modules/SdnDiag.Utilities.psm1:1876
- The session is reused solely by computer name even when the caller supplies an explicit credential. If an earlier session was opened under another identity, the requested credential is silently ignored, so the CIM operation can run with the wrong authorization or fail unexpectedly. Reuse must distinguish session identity, or explicit credentials must force a matching/new session.
if ($currentActiveSessions.ComputerName -contains $objectName -and !$Force) {
$session = ($currentActiveSessions | Where-Object { $_.ComputerName -eq $objectName })[0]
src/modules/SdnDiag.Server.psm1:2940
- When a VM is found but has no realized setting object,
$adaptersremains the host-wide synthetic-adapter query from line 2930. A request for one VM can therefore return every VM's adapters during transient/non-realized states. Initialize the filtered result to empty and only run the host-wide query when-VMNamewas not supplied.
if ($VMName -and $vmCim) {
$vmSettingData = Get-SdnCimAssociatedInstance -InputObject $vmCim -ResultClassName 'Msvm_VirtualSystemSettingData' @cimParams |
Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
if ($vmSettingData) {
$vmPath = $vmSettingData.CimSystemProperties.CimInstance
$adapters = Get-SdnCimAssociatedInstance -InputObject $vmSettingData -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
}
src/modules/SdnDiag.Server.psm1:3151
Get-VMincludes legacy/emulated network adapters, but this replacement populatesNetworkAdaptersfrom synthetic adapters only. ConsequentlyGet-ServerConfigStateomits every legacy NIC from its per-VM diagnostics. Include the associatedMsvm_EmulatedEthernetPortSettingDatainstances asGet-SdnVMNetworkAdapterCimalready does.
# Get network adapters associated with this VM
$networkAdapters = @()
if ($vmSettings) {
$networkAdapters = Get-SdnCimAssociatedInstance -InputObject $vmSettings -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
src/modules/SdnDiag.Server.psm1:3716
- These names do not match the CIM schema: the provider exposes
SecondaryVlanIdArrayandPruneVlanIdArray, whilePvlanModeis numeric (1/2/3). The current output loses promiscuous secondary VLANs and prune data, andRepair-SdnVMNetworkAdapterPortProfilecannot recognizePromiscuous. Translate the CIM properties to the compatibility shape returned by this function.
SecondaryVlanIdList = if ($vlan) { $vlan.SecondaryVlanIdList } else { $null }
PrivateVlanMode = if ($vlan) { $vlan.PvlanMode } else { $null }
PruneVlanIdArray = if ($vlan) { $vlan.PruneEnabledVlanIdArray } else { $null }
src/SdnDiagnostics.psd1:137
- The PR introduces
New-SdnCimSessionandRemove-SdnCimSessionas session-management commands, but neither is exported here. In particular,Remove-SdnCimSessionhas no internal caller, so module consumers cannot use the provided cleanup path for sessions retained by the new public CIM commands. Export the intended public session commands, or make cleanup automatic and document them as private helpers.
'Get-SdnVMCim',
'Get-SdnVMSwitch',
'Get-SdnVMSwitchCim',
| SwitchType = $sw.IOVPreferred | ||
| Notes = $sw.Notes | ||
| BandwidthPercentage = $sw.MaxIOVOffloads |
| # Build lookup of profile settings by port path | ||
| $profileLookup = @{} | ||
| foreach ($profile in $profileSettings) { | ||
| $portPath = $profile.InstanceID -replace '/[^/]+$', '' |
| $existingProfile.ProfileId = $ProfileId.ToString("B") | ||
| $existingProfile.ProfileData = $ProfileData | ||
| $existingProfile.VendorId = $vendorId.ToString("B") | ||
| Set-CimInstance -InputObject $existingProfile -ErrorAction Stop |
| # Get the port name from the allocation | ||
| if ($matchedPort.InstanceID) { | ||
| $object.PortName = $matchedPort.InstanceID |
…injection, CIM write path - Fix InstanceID join pattern: use backslash separator (\C\GUID) matching real Hyper-V format instead of forward-slash stripping across all 6 port setting/profile locations - Eliminate N+1 round trips in Get-SdnVMCim: bulk-query VirtualSystemSettingData and SyntheticEthernetPortSettingData, join locally by VM GUID - Escape WQL injection: apostrophes in switch/VM names no longer break WQL queries (3 locations) - Fix SwitchType/BandwidthPercentage: renamed to accurate CIM field names (IOVPreferred, MaxIOVOffloads) - Fix CimSession array support: Get-SdnCimAssociatedInstance now accepts CimSession[] - Fix PortName semantic: use ElementName from port allocation for VFP compatibility - Fix Set-CimInstance on read-only snapshots: use Hyper-V cmdlet pipeline (Get/Set-VMSwitchExtensionPortFeature) instead of Set-CimInstance for modifying port profiles - Update test mock data to use proper Hyper-V InstanceID format (backslash-delimited) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 220ab25a-0e38-4217-8e3c-e0a2e22fd7d9
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (15)
src/modules/SdnDiag.Server.psm1:524
- The previous collection also exported each adapter's standard
AclListasGet-VM_AclList; this replacement collects only extended ACLs, so basic VM network adapter ACL diagnostics silently disappear fromGet-ServerConfigState. Retain the existing ACL export or add the corresponding CIM query forMsvm_EthernetSwitchPortAclSettingData.
$_.Group | Export-ObjectToFile -FilePath $vmDir.FullName -Prefix $prefix -Name 'Get-VM_ExtendedAclList' -FileType txt -Format List
src/modules/SdnDiag.Utilities.psm1:1876
- Session reuse is keyed only by computer name, so an existing session created under one identity is returned even when the caller explicitly supplies a different
-Credential. The requested credential is silently ignored and subsequent CIM operations run under the prior session's identity. Avoid reusing an arbitrary session when credentials are supplied, or track sessions by both host and identity.
if ($currentActiveSessions.ComputerName -contains $objectName -and !$Force) {
$session = ($currentActiveSessions | Where-Object { $_.ComputerName -eq $objectName })[0]
src/modules/SdnDiag.Server.psm1:3285
- This map assumes MAC addresses are unique and overwrites the earlier port when duplicates exist. Duplicate VM MACs are an explicitly diagnosed failure state in
Test-VMNetAdapterDuplicateMacAddress, so this diagnostic path can attach one adapter's ACLs to another adapter. Join VM adapters by theirInstanceID(and include host identity for multi-host queries) rather than by MAC alone.
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
$macToPort[$portMac] = $port.InstanceID
src/modules/SdnDiag.Server.psm1:3422
- Using the MAC as a unique key causes one port allocation to overwrite another when duplicate MACs exist, so an adapter can be returned with another adapter's isolation settings. This repository explicitly diagnoses duplicate VM MACs, so the read path must remain accurate in that failure state. Use the adapter/port
InstanceIDplus host identity instead.
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
$macToPort[$portMac] = $port.InstanceID
src/modules/SdnDiag.Server.psm1:3554
- This MAC-keyed lookup overwrites ports when two adapters share a MAC address, causing routing-domain data to be associated with the wrong adapter. Duplicate MACs are a supported diagnostic scenario in this codebase; key the join by port/adapter
InstanceIDand host instead of MAC alone.
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
$macToPort[$portMac] = $port.InstanceID
src/modules/SdnDiag.Server.psm1:3684
- A MAC address is not a safe unique key here: duplicate VM MACs are an explicitly diagnosed state, and with multiple computers the same MAC is also common. The last allocation overwrites earlier ones, so
Repair-SdnVMNetworkAdapterPortProfilecan inspect VLAN settings from a different adapter and make the wrong repair decision. Join by adapter/portInstanceIDand host identity.
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
$macToPort[$portMac] = $port.InstanceID
src/modules/SdnDiag.Server.psm1:3864
- Selecting the first host-wide port allocation with a matching MAC can return another adapter's port when MACs are duplicated.
Get-SdnVMNetworkAdapterPortProfile -VMNamemay then report the wrong profile for the requested VM, which also makes the repair comparison unreliable. Match the VM adapter'sInstanceIDto the port allocation (and scope by host) instead of scanning by MAC.
if ($port.Address -and $adapterMac) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
if ($portMac -eq $adapterMac) {
$matchedPort = $port
break
src/modules/SdnDiag.Server.psm1:4115
- Although
$vmNicwas filtered by VM name, this code discards that identity and chooses the first host-wide port with the same MAC. If another VM has a duplicate MAC, the function can update that VM's existing port profile instead of the requested adapter. Match$vmNic.InstanceIDto the allocation (with an equivalent DeviceId mapping for-HostVmNic) before modifying the feature.
foreach ($port in $portAllocations) {
if ($port.Address) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
if ($portMac -eq $formattedMac) {
$matchedPort = $port
break
src/modules/SdnDiag.Server.psm1:521
- This block runs once per VM, but each helper performs host-wide CIM queries and calls
Get-SdnVMNetworkAdapterCim, which itself performs several more host-wide queries. On a host with N VMs this turns the new bulk-query path into four full enumerations per VM and can recreate the timeout behavior this PR is intended to fix. Query each setting type once with-Allbefore the VM loop and group the results for per-VM export.
Get-SdnVMNetworkAdapterExtendedAclCim -VMName $vm.Name |
src/modules/SdnDiag.Server.psm1:3130
Get-SdnVMCimonly adds synthetic adapters toNetworkAdapters, even thoughGet-SdnVMNetworkAdapterCimexplicitly supportsMsvm_EmulatedEthernetPortSettingData. A VM with only legacy adapters is therefore reported with no adapters, andGet-ServerConfigStateskips its per-VM directory entirely. Bulk-query emulated adapters too and merge both classes into the VM lookup.
$allSyntheticAdapters = Get-CimInstance @cimParams -ClassName 'Msvm_SyntheticEthernetPortSettingData'
src/modules/SdnDiag.Server.psm1:3733
PvlanModeis left as its CIM integer, whileOperationModeis converted to the string contract andRepair-SdnVMNetworkAdapterPortProfilecomparesPrivateVlanModewith'Promiscuous'. A real CIM result therefore becomes'3'rather than'Promiscuous', producing incorrect private-VLAN details. Map values 1/2/3 toIsolated/Community/Promiscuous.
PrivateVlanMode = if ($vlan) { $vlan.PvlanMode } else { $null }
src/modules/SdnDiag.Server.psm1:2941
- If the named VM exists but has no realized
Msvm_VirtualSystemSettingData(for example while transitioning),$adaptersremains the host-wide query from line 2932. The-VMNamecall then returns adapters from every VM instead of an empty result for the requested VM. Clear the adapter set when no realized setting is found.
if ($vmSettingData) {
$vmPath = $vmSettingData.CimSystemProperties.CimInstance
$adapters = Get-SdnCimAssociatedInstance -InputObject $vmSettingData -ResultClassName 'Msvm_SyntheticEthernetPortSettingData' @cimParams
}
src/modules/SdnDiag.Server.psm1:4012
- The description says this function sets profiles through CIM, but both update and create paths still use Hyper-V cmdlets; CIM is only used for lookup. This is misleading public help and contradicts the implementation and PR description.
Uses CIM methods against root/virtualization/v2 to set port profile settings
for faster performance compared to Get-VMSwitchExtensionPortFeature/Set-VMSwitchExtensionPortFeature.
src/SdnDiagnostics.psd1:137
- The manifest exports the new CIM query functions but not
New-SdnCimSessionorRemove-SdnCimSession, although the PR presents both as new session-management commands and the latter is otherwise never called. After importingSdnDiagnostics, users cannot invoke the documented cleanup command, leaving the persistentSdnDiag-Cim-*sessions without a supported cleanup path. Export both commands or perform cleanup internally.
'Get-SdnVMSwitchCim',
src/modules/SdnDiag.Utilities.psm1:1944
- A failed
Remove-CimSessionis only logged as a warning and is not written to the error stream, so callers cannot detect that cleanup failed and the session remains open. Preserve the exception in the standard error path as the outer catch does.
catch {
"Unable to remove CIM session {0} for {1}. Error: {2}" -f $session.Name, $session.ComputerName, $_.Exception.Message | Trace-Output -Level:Warning
}
Summary
Implements CIM-based methods to replace slow Hyper-V cmdlets (
Get-VM,Get-VMNetworkAdapter,Get-VMSwitch, etc.) for significantly faster VM and network adapter enumeration. Resolves #404.Changes
New CIM Functions (SdnDiag.Server)
Get-SdnVMCim— ReplacesGet-VMwith direct CIM query againstMsvm_ComputerSystem. Bulk-queries settings and adapters (no N+1 round trips).Get-SdnVMNetworkAdapterCim— ReplacesGet-VMNetworkAdapterwith CIM queries againstMsvm_SyntheticEthernetPortSettingData/Msvm_InternalEthernetPort. Supports-VMName,-MacAddress,-All,-ManagementOS.Get-SdnVMSwitchCim— ReplacesGet-VMSwitchwith CIM query againstMsvm_VirtualEthernetSwitch.Get-SdnCimAssociatedInstance— Testable wrapper aroundGet-CimAssociatedInstance.New CIM Port Setting Functions
Get-SdnVMNetworkAdapterVlanCim— VLAN settings viaMsvm_EthernetSwitchPortVlanSettingDataGet-SdnVMNetworkAdapterIsolationCim— Isolation settings viaMsvm_EthernetSwitchPortIsolationSettingDataGet-SdnVMNetworkAdapterExtendedAclCim— Extended ACL settings viaMsvm_EthernetSwitchPortExtendedAclSettingDataGet-SdnVMNetworkAdapterRoutingDomainCim— Routing domain mappings viaMsvm_EthernetSwitchPortRoutingDomainSettingDataCIM Session Management (SdnDiag.Utilities)
New-SdnCimSession— Centralized CIM session creation with caching (mirrorsNew-PSRemotingSessionpattern)Remove-SdnCimSession— Cleanup ofSdnDiag-Cim-*sessionsUpdated Callers
Get-ServerConfigState— Uses CIM functions for VM/adapter/port-setting data collectionGet-SdnVMNetworkAdapterPortProfile— Rewritten to useGet-SdnVMNetworkAdapterCiminternallySet-SdnVMNetworkAdapterPortProfile— CIM for reads, Hyper-V cmdlet pipeline for writes (CIM instances are read-only snapshots)Repair-SdnVMNetworkAdapterPortProfile— VLAN read path usesGet-SdnVMNetworkAdapterVlanCimKey Design Decisions
Microsoft:VMGUID\PortGUID\C\FeatureGUID)Get-SdnVMNetworkAdapterandGet-SdnVMSwitchpreserved for existing callersSet-CimInstancereplaced withGet/Set-VMSwitchExtensionPortFeatureHyper-V pipeline for port profile modificationsTests