Skip to content

Add CIM-based Get-SdnVMCim and Get-SdnVMNetworkAdapterCim for faster VM enumeration - #605

Open
Adam Rudell (arudell) wants to merge 6 commits into
microsoft:mainfrom
arudell:arudell-microsoft-cim-vm-adapter-perf
Open

Add CIM-based Get-SdnVMCim and Get-SdnVMNetworkAdapterCim for faster VM enumeration#605
Adam Rudell (arudell) wants to merge 6 commits into
microsoft:mainfrom
arudell:arudell-microsoft-cim-vm-adapter-perf

Conversation

@arudell

@arudell Adam Rudell (arudell) commented Jul 15, 2026

Copy link
Copy Markdown
Member

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 — Replaces Get-VM with direct CIM query against Msvm_ComputerSystem. Bulk-queries settings and adapters (no N+1 round trips).
  • Get-SdnVMNetworkAdapterCim — Replaces Get-VMNetworkAdapter with CIM queries against Msvm_SyntheticEthernetPortSettingData / Msvm_InternalEthernetPort. Supports -VMName, -MacAddress, -All, -ManagementOS.
  • Get-SdnVMSwitchCim — Replaces Get-VMSwitch with CIM query against Msvm_VirtualEthernetSwitch.
  • Get-SdnCimAssociatedInstance — Testable wrapper around Get-CimAssociatedInstance.

New CIM Port Setting Functions

  • Get-SdnVMNetworkAdapterVlanCim — VLAN settings via Msvm_EthernetSwitchPortVlanSettingData
  • Get-SdnVMNetworkAdapterIsolationCim — Isolation settings via Msvm_EthernetSwitchPortIsolationSettingData
  • Get-SdnVMNetworkAdapterExtendedAclCim — Extended ACL settings via Msvm_EthernetSwitchPortExtendedAclSettingData
  • Get-SdnVMNetworkAdapterRoutingDomainCim — Routing domain mappings via Msvm_EthernetSwitchPortRoutingDomainSettingData

CIM Session Management (SdnDiag.Utilities)

  • New-SdnCimSession — Centralized CIM session creation with caching (mirrors New-PSRemotingSession pattern)
  • Remove-SdnCimSession — Cleanup of SdnDiag-Cim-* sessions

Updated Callers

  • Get-ServerConfigState — Uses CIM functions for VM/adapter/port-setting data collection
  • Get-SdnVMNetworkAdapterPortProfile — Rewritten to use Get-SdnVMNetworkAdapterCim internally
  • Set-SdnVMNetworkAdapterPortProfile — CIM for reads, Hyper-V cmdlet pipeline for writes (CIM instances are read-only snapshots)
  • Repair-SdnVMNetworkAdapterPortProfile — VLAN read path uses Get-SdnVMNetworkAdapterVlanCim

Key Design Decisions

  • InstanceID join pattern: Port features joined to port allocations using backslash-delimited InstanceID format (Microsoft:VMGUID\PortGUID\C\FeatureGUID)
  • Bulk queries over N+1: All functions bulk-query CIM classes and join locally instead of per-VM association calls
  • WQL injection prevention: Apostrophes in VM/switch names are escaped in WQL filters
  • Backward compatibility: Original Get-SdnVMNetworkAdapter and Get-SdnVMSwitch preserved for existing callers
  • Write path safety: Set-CimInstance replaced with Get/Set-VMSwitchExtensionPortFeature Hyper-V pipeline for port profile modifications

Tests

  • 79 Pester tests (30 Server + 7 Utilities + existing), all passing
  • Covers CIM core functions, port setting functions, port profile, repair scenarios, session management

Copilot AI balanced review requested due to automatic review settings August 13, 2026 19:29
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
@arudell
Adam Rudell (arudell) force-pushed the arudell-microsoft-cim-vm-adapter-perf branch from e7216d8 to 3812f9f Compare August 13, 2026 19:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 than Set-CimInstance. Therefore the existing-profile path cannot update a port profile. Retrieve Msvm_EthernetSwitchPortProfileSettingData and invoke the appropriate feature-modification method, or retain Set-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 $vmNic was 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.InstanceID and the allocation's Parent association 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
Comment on lines +1869 to +1870
if ($currentActiveSessions.ComputerName -contains $objectName -and !$Force) {
$session = ($currentActiveSessions | Where-Object { $_.ComputerName -eq $objectName })[0]
Comment on lines +1893 to +1894
"Unable to create CIM session to {0}. Error: {1}" -f $objectName, $_.Exception.Message | Trace-Output -Level:Error
$_ | Trace-Exception
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +2628 to +2629
[Parameter(Mandatory = $false)]
[Microsoft.Management.Infrastructure.CimSession]$CimSession
}
}

function Get-SdnVMNetworkAdapterCim {
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +2872 to +2875
$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 {
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +3161 to +3162
# Get port security settings which contain the profile data
$securitySettings = Get-CimInstance @cimParams -ClassName 'Msvm_EthernetSwitchPortSecuritySettingData'
Comment on lines +3200 to +3204
if ($port.Address -and $adapterMac) {
$portMac = Format-SdnMacAddress -MacAddress $port.Address
if ($portMac -eq $adapterMac) {
$matchedPort = $port
break
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +3050 to +3052
# 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' }
Copilot AI review requested due to automatic review settings August 13, 2026 19:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 two ComputerName values—or forwarding the session array from Get-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.psd1 under FunctionsToExport (the manifest currently exports only the original adapter command). Consequently, users importing SdnDiagnostics cannot call Get-SdnVMNetworkAdapterCim. Add the manifest export and its required offline Pester coverage.
function Get-SdnVMNetworkAdapterCim {

src/modules/SdnDiag.Server.psm1:2807

  • MacAddress filtering 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 VMName disables 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.psd1 FunctionsToExport, 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-ServerConfigState now 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_EthernetSwitchPortSecuritySettingData does not contain port-profile fields. Those are ProfileId and ProfileData on Msvm_EthernetSwitchPortProfileSettingData. As written, every returned profile has empty values, and Repair-SdnVMNetworkAdapterPortProfile subsequently fails when parsing the missing ProfileId. 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 through Msvm_VirtualEthernetSwitchManagementService.ModifyFeatureSettings rather than Set-CimInstance. On a port that enters this branch, profile repair fails instead of applying the requested profile. Retrieve Msvm_EthernetSwitchPortProfileSettingData, update ProfileId/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.CimSession contains 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-SdnVMNetworkAdapterPortProfile signature now adds remote connectivity parameters, but tests/offline/Server.Tests.ps1 only mocks this command while testing Repair-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]

Comment on lines +2855 to +2857
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
Copilot AI review requested due to automatic review settings August 13, 2026 19:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and VendorId belong to Msvm_EthernetSwitchPortProfileSettingData (and are not named PortProfileId/PortProfileData), so returned profile objects remain empty and callers such as the repair path can fail when parsing ProfileId. 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, or PortProfileVendorId properties, and Hyper-V feature settings must be committed through Msvm_VirtualEthernetSwitchManagementService.ModifyFeatureSettings, rather than Set-CimInstance. Use the associated Msvm_EthernetSwitchPortProfileSettingData object (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 Server loses 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-SdnVMNetworkAdapterCim is presented as a new callable function, but it was not added to src/SdnDiagnostics.psd1 FunctionsToExport. After importing SdnDiagnostics, consumers cannot invoke it; only internal module code and InModuleScope tests can. Export the function in the manifest.
function Get-SdnVMNetworkAdapterCim {

src/modules/SdnDiag.Server.psm1:3005

  • Get-SdnVMCim is not listed in src/SdnDiagnostics.psd1 FunctionsToExport, 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 -VMName is 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. Retrieve Msvm_EmulatedEthernetPortSettingData from 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 MacAddress filter 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 Stop works 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. Model Msvm_EthernetSwitchPortProfileSettingData with ProfileId, ProfileData, and VendorId, 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}'

Comment on lines +2607 to +2623
[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)
@arudell
Adam Rudell (arudell) marked this pull request as ready for review August 13, 2026 20:12
@arudell
Adam Rudell (arudell) requested a review from a team as a code owner August 13, 2026 20:12
- 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
Copilot AI review requested due to automatic review settings August 13, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-VMNetworkAdapter once 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

  • VMName is 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

  • VMName is appended to the WQL filter without escaping. A valid VM name containing an apostrophe makes the query malformed, so Get-SdnVMCim -VMName fails 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-SdnVMCim perform 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-SdnVMNetworkAdapterCim explicitly supports Msvm_EmulatedEthernetPortSettingData, but VMs with legacy adapters will lose those adapters from Get-SdnVMCim and 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 InstanceID is the port allocation ID plus a slash suffix, but CIM InstanceID is an opaque key and Hyper-V defines Msvm_EthernetPortSettingDataComponent to associate a port allocation with its feature settings. On real hosts this derived key can miss the profile and return empty ProfileId/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 $vmNic is 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 InstanceID is derived from the port allocation ID by removing a slash suffix. Hyper-V exposes the port-to-feature relationship through Msvm_EthernetPortSettingDataComponent; if the opaque IDs do not share this shape, $existingProfile remains 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's InstanceID/Parent relationship 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 ComputerName values, this filtered query can return one VM object per host, but the wrapper forwards $vmCim as a single InputObject to a cmdlet that accepts one CimInstance. The advertised multi-host -VMName path 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

  • ComputerName is documented and typed as an array in the new public callers, so they can splat multiple sessions here, but this wrapper only accepts one CimSession. Multi-host calls to Get-SdnVMCim or VM-filtered adapter queries will fail parameter conversion before reaching Get-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), $adapters is left as the earlier host-wide synthetic-adapter query. The -VMName call 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: IOVPreferred is an SR-IOV preference flag, not a switch type, and MaxIOVOffloads is 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 -All contract means all VM adapters, while the new helper's -All explicitly 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
            }

Comment thread src/modules/SdnDiag.Server.psm1 Outdated
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
Copilot AI review requested due to automatic review settings August 13, 2026 21:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 InstanceID values. The relationship is represented by Msvm_EthernetPortSettingDataComponent; because the resulting key does not equal the allocation InstanceID, 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 through Msvm_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; its InstanceID is not a forward-slash child path. This key therefore never matches $port.InstanceID, causing every configured adapter to be reported as Untagged. That also makes Repair-SdnVMNetworkAdapterPortProfile miss 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 by Msvm_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: IOVPreferred is a Boolean SR-IOV preference, not the Hyper-V switch type (External/Internal/Private), and MaxIOVOffloads is not a bandwidth percentage. Because this function is exported as a Get-VMSwitch alternative, 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-VMNetworkAdapter and 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-SdnVMCim populates NetworkAdapters from synthetic adapters only, so VMs with legacy/emulated NICs lose those adapters compared with Get-VM. Get-SdnVMNetworkAdapterCim explicitly handles Msvm_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, PvlanMode is returned as its raw CIM integer. Repair-SdnVMNetworkAdapterPortProfile compares this property with 'Promiscuous', so it takes the wrong detail path for promiscuous PVLANs, and this exported replacement is inconsistent with Get-VMNetworkAdapterVlan. Map values 1/2/3 to Isolated/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.ps1 adds 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

Comment thread src/modules/SdnDiag.Server.psm1 Outdated
[Parameter(Mandatory = $true, ParameterSetName = 'Management')]
[switch]$ManagementOS
[Parameter(Mandatory = $false)]
[Microsoft.Management.Infrastructure.CimSession]$CimSession
Comment on lines +3246 to +3250
# Build lookup: port InstanceID -> list of ACL settings
$aclByPort = @{}
foreach ($acl in $aclSettings) {
$portPath = $acl.InstanceID -replace '/[^/]+$', ''
if (-not $aclByPort.ContainsKey($portPath)) {
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +4116 to +4119
$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
Copilot AI review requested due to automatic review settings August 13, 2026 21:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AclList as Get-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 /feature suffix. 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 /feature suffix. 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 /feature suffix. 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 /feature suffix. This expression leaves the real VLAN-setting InstanceID unchanged; the lookup then misses configured VLANs and reports them as Untagged, which prevents Repair-SdnVMNetworkAdapterPortProfile from 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 /profile parsing 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, $adapters remains 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 -VMName was 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-VM includes legacy/emulated network adapters, but this replacement populates NetworkAdapters from synthetic adapters only. Consequently Get-ServerConfigState omits every legacy NIC from its per-VM diagnostics. Include the associated Msvm_EmulatedEthernetPortSettingData instances as Get-SdnVMNetworkAdapterCim already 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 SecondaryVlanIdArray and PruneVlanIdArray, while PvlanMode is numeric (1/2/3). The current output loses promiscuous secondary VLANs and prune data, and Repair-SdnVMNetworkAdapterPortProfile cannot recognize Promiscuous. 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-SdnCimSession and Remove-SdnCimSession as session-management commands, but neither is exported here. In particular, Remove-SdnCimSession has 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',

Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +2741 to +2743
SwitchType = $sw.IOVPreferred
Notes = $sw.Notes
BandwidthPercentage = $sw.MaxIOVOffloads
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
# Build lookup of profile settings by port path
$profileLookup = @{}
foreach ($profile in $profileSettings) {
$portPath = $profile.InstanceID -replace '/[^/]+$', ''
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
$existingProfile.ProfileId = $ProfileId.ToString("B")
$existingProfile.ProfileData = $ProfileData
$existingProfile.VendorId = $vendorId.ToString("B")
Set-CimInstance -InputObject $existingProfile -ErrorAction Stop
Comment thread src/modules/SdnDiag.Server.psm1 Outdated
Comment on lines +3874 to +3876
# 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
Copilot AI review requested due to automatic review settings August 14, 2026 17:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AclList as Get-VM_AclList; this replacement collects only extended ACLs, so basic VM network adapter ACL diagnostics silently disappear from Get-ServerConfigState. Retain the existing ACL export or add the corresponding CIM query for Msvm_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 their InstanceID (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 InstanceID plus 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 InstanceID and 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-SdnVMNetworkAdapterPortProfile can inspect VLAN settings from a different adapter and make the wrong repair decision. Join by adapter/port InstanceID and 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 -VMName may then report the wrong profile for the requested VM, which also makes the repair comparison unreliable. Match the VM adapter's InstanceID to 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 $vmNic was 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.InstanceID to 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 -All before the VM loop and group the results for per-VM export.
                    Get-SdnVMNetworkAdapterExtendedAclCim -VMName $vm.Name |

src/modules/SdnDiag.Server.psm1:3130

  • Get-SdnVMCim only adds synthetic adapters to NetworkAdapters, even though Get-SdnVMNetworkAdapterCim explicitly supports Msvm_EmulatedEthernetPortSettingData. A VM with only legacy adapters is therefore reported with no adapters, and Get-ServerConfigState skips 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

  • PvlanMode is left as its CIM integer, while OperationMode is converted to the string contract and Repair-SdnVMNetworkAdapterPortProfile compares PrivateVlanMode with 'Promiscuous'. A real CIM result therefore becomes '3' rather than 'Promiscuous', producing incorrect private-VLAN details. Map values 1/2/3 to Isolated/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), $adapters remains the host-wide query from line 2932. The -VMName call 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-SdnCimSession or Remove-SdnCimSession, although the PR presents both as new session-management commands and the latter is otherwise never called. After importing SdnDiagnostics, users cannot invoke the documented cleanup command, leaving the persistent SdnDiag-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-CimSession is 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
            }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Returning list of VMs and VMNetworkAdapters is slow

2 participants