├── tests ├── sanity.txt ├── setup │ ├── initiate-tests.ps1 │ ├── install.ps1 │ └── deploy.ps1 ├── unit │ └── unit.tests.ps1 └── results │ └── TestResults.xml ├── capture ├── etl2pcapng.exe └── Readme.md ├── LICENSE ├── appveyor.yml ├── README.md ├── SECURITY.md ├── DataCenterBridging.psd1 ├── .gitignore └── DataCenterBridging.psm1 /tests/sanity.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/DataCenterBridging/HEAD/tests/sanity.txt -------------------------------------------------------------------------------- /capture/etl2pcapng.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/DataCenterBridging/HEAD/capture/etl2pcapng.exe -------------------------------------------------------------------------------- /capture/Readme.md: -------------------------------------------------------------------------------- 1 | # Notes on ETL2PCAPNG 2 | 3 | ETL2PCAPNG is a conversion tool written to convert ETL files to a wireshark readable pcapng extension. The project is can be found on github here: https://github.com/microsoft/etl2pcapng -------------------------------------------------------------------------------- /tests/setup/initiate-tests.ps1: -------------------------------------------------------------------------------- 1 | # Invoke Pester to run tests, then save the results in NUnitXML to populate the AppVeyor tests section 2 | # Pester : https://github.com/pester/Pester/wiki 3 | # Pester Code Coverage : https://info.sapien.com/index.php/scripting/scripting-modules/testing-pester-code-coverage 4 | 5 | Import-Module Pester -RequiredVersion '4.9.0' 6 | New-Item -Path .\tests -Name results -ItemType Directory -Force | Out-Null 7 | 8 | $testResultPath = '.\tests\results\TestResults.xml' 9 | # This is a manifest so no code coverage is possible. Original line kept below: 10 | #...\results\TestsResults.xml -PassThru -CodeCoverage .\MSFT.Network.Tools.psd1 11 | $res = Invoke-Pester -Path ".\tests\unit" -OutputFormat NUnitXml -OutputFile $testResultPath -PassThru 12 | 13 | (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultPath)) 14 | 15 | if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed." } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /tests/unit/unit.tests.ps1: -------------------------------------------------------------------------------- 1 | $DataFile = Import-PowerShellDataFile .\$($env:repoName).psd1 -ErrorAction SilentlyContinue 2 | $TestModule = Test-ModuleManifest .\$($env:repoName).psd1 -ErrorAction SilentlyContinue 3 | 4 | Describe "$($env:APPVEYOR_BUILD_FOLDER)-Manifest" { 5 | Context Validation { 6 | It "[Import-PowerShellDataFile] - $($env:repoName).psd1 is a valid PowerShell Data File" { 7 | $DataFile | Should Not BeNullOrEmpty 8 | } 9 | 10 | It "[Test-ModuleManifest] - $($env:repoName).psd1 should pass the basic test" { 11 | $TestModule | Should Not BeNullOrEmpty 12 | } 13 | 14 | Import-Module .\$($env:repoName).psd1 -ErrorAction SilentlyContinue 15 | $Module = Get-Module $($env:repoName) -ErrorAction SilentlyContinue 16 | 17 | 'DCBNetQosFlowControl', 'DCBNetAdapterQos', 'DCBNetQosDcbxSetting', 18 | 'DCBNetQosTrafficClass', 'DCBNetQosPolicy' | ForEach-Object { 19 | It "Should have exported the DSC Resource: $_" { 20 | $_ -in $module.ExportedDSCResources | Should Be $true 21 | } 22 | } 23 | 24 | 'Test-FabricInfo', 'Get-FabricInfo', 'Enable-FabricInfo', 'Start-FabricCapture' | ForEach-Object { 25 | It "Should have an available command: $_" { 26 | $module.ExportedCommands.ContainsKey($_) | Should be $true 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/setup/install.ps1: -------------------------------------------------------------------------------- 1 | git.exe clone -q https://github.com/PowerShell/DscResource.Tests 2 | 3 | Import-Module -Name "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1" 4 | Invoke-AppveyorInstallTask 5 | 6 | [string[]]$PowerShellModules = @("Pester", 'posh-git', 'PSScriptAnalyzer') 7 | 8 | $ModuleManifest = Test-ModuleManifest .\$($env:RepoName).psd1 -ErrorAction SilentlyContinue 9 | $repoRequiredModules = $ModuleManifest.RequiredModules.Name 10 | $repoRequiredModules += $ModuleManifest.PrivateData.PSData.ExternalModuleDependencies 11 | 12 | If ($repoRequiredModules) { $PowerShellModules += $repoRequiredModules } 13 | 14 | # This section is taken care of by Invoke-AppVeyorInstallTask 15 | <#[string[]]$PackageProviders = @('NuGet', 'PowerShellGet') 16 | 17 | # Install package providers for PowerShell Modules 18 | ForEach ($Provider in $PackageProviders) { 19 | If (!(Get-PackageProvider $Provider -ErrorAction SilentlyContinue)) { 20 | Install-PackageProvider $Provider -Force -ForceBootstrap -Scope CurrentUser 21 | } 22 | }#> 23 | 24 | # Feature Installation 25 | 26 | $serverFeatureList = @('Hyper-V') 27 | 28 | If ($PowerShellModules -contains 'FailoverClusters') { 29 | $serverFeatureList += 'RSAT-Clustering-Mgmt', 'RSAT-Clustering-PowerShell' 30 | } 31 | 32 | $BuildSystem = Get-CimInstance -ClassName 'Win32_OperatingSystem' 33 | 34 | ForEach ($Module in $PowerShellModules) { 35 | If ($Module -eq 'FailoverClusters') { 36 | Switch -Wildcard ($BuildSystem.Caption) { 37 | '*Windows 10*' { 38 | Write-Output 'Build System is Windows 10' 39 | Write-Output "Not Implemented" 40 | 41 | # Get FailoverCluster Capability Name and Install on W10 Builds 42 | $capabilityName = (Get-WindowsCapability -Online | Where-Object Name -like *RSAT*FailoverCluster.Management*).Name 43 | Add-WindowsCapability -Name $capabilityName -Online 44 | } 45 | 46 | Default { 47 | Write-Output "Build System is $($BuildSystem.Caption)" 48 | Install-WindowsFeature -Name $serverFeatureList -IncludeManagementTools | Out-Null 49 | } 50 | } 51 | } 52 | ElseIf ($Module -eq 'Pester') { 53 | Install-Module $Module -Scope AllUsers -Force -Repository PSGallery -AllowClobber -SkipPublisherCheck -RequiredVersion 4.9.0 54 | Import-Module $Module -RequiredVersion 4.9.0 55 | } 56 | else { 57 | Install-Module $Module -Scope AllUsers -Force -Repository PSGallery -AllowClobber 58 | Import-Module $Module 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # YAML Reference Guide : https://www.appveyor.com/docs/appveyor-yml/ 2 | # Environmental Variables Guide : https://www.appveyor.com/docs/environment-variables/ 3 | # YAML Validator : https://ci.appveyor.com/tools/validate-yaml 4 | # AppVeyor Build Pipeline : https://www.appveyor.com/docs/build-configuration/ 5 | # GitHub push with tokens : https://www.appveyor.com/docs/how-to/git-push/ 6 | 7 | # Repo cloned into this folder on the build worker 8 | clone_folder: c:\projects\DataCenterBridging 9 | 10 | # Date-based versioning. 11 | # Version will be of format: yyyy.MM.dd.Build Number 12 | 13 | init: 14 | - ps: $Env:repoName = $($env:APPVEYOR_REPO_NAME.Split('/')[1]) 15 | - ps: Update-AppveyorBuild -Version "$(Get-Date -format yyyy.MM.dd).$env:appveyor_build_number" 16 | - ps: $Env:BuildVersion = "$(Get-Date -format yyyy.MM.dd).$env:appveyor_build_number" 17 | 18 | # Install script prior to running tests 19 | install: 20 | - ps: . .\tests\setup\install.ps1 21 | 22 | # Initiate tests 23 | test_script: 24 | - ps: . .\tests\setup\initiate-tests.ps1 25 | 26 | # finalize build 27 | deploy_script: 28 | - ps: . .\tests\setup\deploy.ps1 29 | 30 | version: 0.0.0.{build} 31 | 32 | image: 33 | - Visual Studio 2017 34 | 35 | # Environment variables for PowerShell Gallery (NuGetAPIKey) and GitHub (GitHubKey) API key for publishing updates 36 | # - The "secure:" value is the Appveyor encryption of the key 37 | # - GitHub update occurs to ensure that the module version is incremented based on the build number 38 | 39 | #CoreNetBuilder 40 | environment: 41 | NuGetApiKey: 42 | secure: AAdhaP/N6f3sMBR2vaN7IS9Mzv4z0YcbqtD4MHxT0m2k4/sDKORYljLFs/BmOq1q 43 | GitHubKey: 44 | secure: 4VTkUGIAUqMtDSbrIrbn856kPmOLVYNSSy/NwsnQajL+7o+xpoqWZcTpYfEXco2/ 45 | 46 | # Disable automatic builds; Without this, the following error shows up: 47 | # "Specify a project or solution file. The directory does not contain a project or solution file." 48 | build: "off" 49 | 50 | max_jobs: 1 51 | 52 | # Ignore testing a commit if specific strings used in commit message: updated readme, update readme, update docs, update version, update appveyor 53 | skip_commits: 54 | message: /updated readme.*|update readme.*s|update docs.*|update version.*|update appveyor.*/ 55 | files: 56 | - README.md 57 | 58 | # There's no need to alter the build number for a Pull Request (PR) since they don't modify anything 59 | pull_requests: 60 | do_not_increment_build_number: true 61 | 62 | #on_finish: 63 | # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/ohvjnqqu2o9ccq60?svg=true)](https://ci.appveyor.com/project/MSFTCoreNet/datacenterbridging) 2 | [![downloads](https://img.shields.io/powershellgallery/dt/datacenterbridging.svg?label=downloads)](https://www.powershellgallery.com/packages/datacenterbridging) 3 | 4 | # Description 5 | 6 | This module includes two primary capabilities: 7 | 8 | - DSC PowerShell module intended to deploy Data Center Bridging settings using https://aka.ms/Validate-DCB 9 | - Link Layer Discovery Protocol (LLDP) 802.1AB parser for Windows for the following organizationally specific subtypes 10 | 11 | | Name | Organization | Subtype | 12 | | :------------- | :----------: | -----------: | 13 | | Port VLAN (Native VLAN) | 802.1 | 1 | 14 | | VLAN Name | 802.1 | 3 | 15 | | Priority-based Flow Control Configuration (PFC) | 802.1 | B | 16 | | Maximum Frame Size | 802.3 | 4 | 17 | 18 | ## :star: More by the Microsoft Core Networking team 19 | 20 | Find more from the Core Networking team using the [MSFTNet](https://github.com/topics/msftnet) topic 21 | 22 | # Installation 23 | 24 | This module is part of MSFT.Network.Tools which can be installed using this command: 25 | ```Install-Module MSFT.Network.Tools``` 26 | 27 | Or install this module individually using this command: 28 | ```Install-Module DataCenterBridging``` 29 | 30 | To see all modules from the Microsoft Core Networking team, please use: 31 | ```Find-Module -Tag MSFTNet``` 32 | 33 | # Usage 34 | 35 | Please refer to the following blog for information on how to use the FabricInfo tools [Troubleshooting Switch Misconfiguration](https://techcommunity.microsoft.com/t5/networking-blog/troubleshooting-switch-misconfiguration/ba-p/2223614) 36 | 37 | # Contributing 38 | 39 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 40 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 41 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 42 | 43 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 44 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 45 | provided by the bot. You will only need to do this once across all repos using our CLA. 46 | 47 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 48 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 49 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 50 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/setup/deploy.ps1: -------------------------------------------------------------------------------- 1 | git config --global credential.helper store 2 | 3 | Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:GitHubKey):x-oauth-basic@github.com`n" 4 | 5 | git config --global user.email "dcuomo@outlook.com" 6 | git config --global user.name "Dan Cuomo" 7 | git config --global core.autocrlf false 8 | git config --global core.safecrlf false 9 | 10 | # Line break for readability in AppVeyor console 11 | Write-Host -Object '' 12 | 13 | # Make sure we're using the Master branch and that it's not a pull request 14 | # Environmental Variables Guide: https://www.appveyor.com/docs/environment-variables/ 15 | if ($env:APPVEYOR_REPO_BRANCH -ne 'master') 16 | { 17 | Write-Warning -Message "Skipping version increment and publish for branch $env:APPVEYOR_REPO_BRANCH" 18 | } 19 | elseif ($env:APPVEYOR_PULL_REQUEST_NUMBER -gt 0) 20 | { 21 | Write-Warning -Message "Skipping version increment and publish for pull request #$env:APPVEYOR_PULL_REQUEST_NUMBER" 22 | } 23 | else 24 | { 25 | # We're going to add 1 to the revision value since a new commit has been merged to Master 26 | # This means that the major / minor / build values will be consistent across GitHub and the Gallery 27 | Try 28 | { 29 | # This is where the module manifest lives 30 | $manifestPath = ".\$($env:RepoName).psd1" 31 | 32 | # Start by importing the manifest to determine the version, then add 1 to the revision 33 | $manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction SilentlyContinue 34 | [System.Version]$version = $manifest.Version 35 | Write-Output "Old Version: $version" 36 | [String]$newVersion = "$(Get-Date -format yyyy.MM.dd).$env:appveyor_build_number" 37 | Write-Output "New Version: $newVersion" 38 | 39 | # Update the manifest with the new version value and fix the weird string replace bug 40 | #$functionList = ((Get-ChildItem -Path .\$($env:RepoName)).BaseName) 41 | $splat = @{ 42 | 'Path' = $manifestPath 43 | 'ModuleVersion' = $newVersion 44 | #'FunctionsToExport' = $functionList 45 | 'Copyright' = "(c) $( (Get-Date).Year ) Inc. All rights reserved." 46 | } 47 | 48 | Update-ModuleManifest @splat -ErrorAction SilentlyContinue 49 | (Get-Content -Path $manifestPath) -replace "PSGet_$($env:RepoName)", "$($env:RepoName)" | Set-Content -Path $manifestPath 50 | (Get-Content -Path $manifestPath) -replace 'NewManifest', "$($env:RepoName)" | Set-Content -Path $manifestPath 51 | #(Get-Content -Path $manifestPath) -replace 'FunctionsToExport = ', 'FunctionsToExport = @(' | Set-Content -Path $manifestPath -Force 52 | #(Get-Content -Path $manifestPath) -replace "$($functionList[-1])'", "$($functionList[-1])')" | Set-Content -Path $manifestPath -Force 53 | 54 | Get-FileHash .\DataCenterBridging.psd1, .\DataCenterBridging.psm1 -Algorithm SHA512 | Format-Table -AutoSize | Out-File ".\tests\sanity.txt" 55 | } 56 | catch 57 | { 58 | throw $_ 59 | } 60 | 61 | # Publish the new version to the PowerShell Gallery 62 | Try 63 | { 64 | # Build a splat containing the required details and make sure to Stop for errors which will trigger the catch 65 | $PM = @{ 66 | Path = '.' 67 | NuGetApiKey = $env:NuGetApiKey 68 | ErrorAction = 'Stop' 69 | Force = $true 70 | } 71 | 72 | Publish-Module @PM 73 | Write-Host "$($env:RepoName) PowerShell Module version $newVersion published to the PowerShell Gallery." -ForegroundColor Cyan 74 | } 75 | Catch 76 | { 77 | Write-Warning "Publishing update $newVersion to the PowerShell Gallery failed." 78 | throw $_ 79 | } 80 | 81 | # Publish the new version back to Master on GitHub 82 | Try 83 | { 84 | # Set up a path to the git.exe cmd, import posh-git to give us control over git, and then push changes to GitHub 85 | # Note that "update version" is included in the appveyor.yml file's "skip a build" regex to avoid a loop 86 | $env:Path += ";$env:ProgramFiles\Git\cmd" 87 | Import-Module posh-git -ErrorAction Stop 88 | git checkout master -q 89 | git add --all 90 | git status 91 | git commit -s -m "Update version to $newVersion" 92 | git push origin master -q 93 | Write-Host "$($env:RepoName) PowerShell Module version $newVersion published to GitHub." -ForegroundColor Cyan 94 | } 95 | Catch 96 | { 97 | Write-Warning "Publishing update $newVersion to GitHub failed." 98 | throw $_ 99 | } 100 | } -------------------------------------------------------------------------------- /tests/results/TestResults.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /DataCenterBridging.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'DataCenterBridging' 3 | # 4 | # Generated by: Dan Cuomo 5 | # 6 | # Generated on: 8/18/2021 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'DataCenterBridging.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '2021.8.18.41' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '0a1096ac-ec89-4ab7-8abd-e2ab457c6bd6' 22 | 23 | # Author of this module 24 | Author = 'Dan Cuomo' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Microsoft' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2021 Inc. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'This module provides DSCResources to configure Data Center Bridging on your system(s) gather LLDP Information' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'Test-FabricInfo', 'Get-FabricInfo', 'Enable-FabricInfo', 73 | 'Start-FabricCapture' 74 | 75 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 76 | CmdletsToExport = @() 77 | 78 | # Variables to export from this module 79 | VariablesToExport = '*' 80 | 81 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 82 | AliasesToExport = @() 83 | 84 | # DSC resources to export from this module 85 | DscResourcesToExport = 'DCBNetQosFlowControl', 'DCBNetAdapterQos', 'DCBNetQosDcbxSetting', 86 | 'DCBNetQosPolicy', 'DCBNetQosTrafficClass' 87 | 88 | # List of all modules packaged with this module 89 | # ModuleList = @() 90 | 91 | # List of all files packaged with this module 92 | # FileList = @() 93 | 94 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 95 | PrivateData = @{ 96 | 97 | PSData = @{ 98 | 99 | # Tags applied to this module. These help with module discovery in online galleries. 100 | Tags = 'MSFTNet','Validate-DCB','LLDP' 101 | 102 | # A URL to the license for this module. 103 | # LicenseUri = '' 104 | 105 | # A URL to the main website for this project. 106 | ProjectUri = 'https://github.com/microsoft/DataCenterBridging' 107 | 108 | # A URL to an icon representing this module. 109 | # IconUri = '' 110 | 111 | # ReleaseNotes of this module 112 | # ReleaseNotes = '' 113 | 114 | # Prerelease string of this module 115 | # Prerelease = '' 116 | 117 | # Flag to indicate whether the module requires explicit user acceptance for install/update 118 | # RequireLicenseAcceptance = $false 119 | 120 | # External dependent modules of this module 121 | # ExternalModuleDependencies = @() 122 | 123 | } # End of PSData hashtable 124 | 125 | } # End of PrivateData hashtable 126 | 127 | # HelpInfo URI of this module 128 | # HelpInfoURI = '' 129 | 130 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 131 | # DefaultCommandPrefix = '' 132 | 133 | } 134 | 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | DSCResource.Tests/ 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | **/Properties/launchSettings.json 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush 297 | .cr/ 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ 333 | -------------------------------------------------------------------------------- /DataCenterBridging.psm1: -------------------------------------------------------------------------------- 1 | #region DCB DSC Resources 2 | enum Ensure { 3 | Absent 4 | Present 5 | } 6 | 7 | [DscResource()] 8 | Class DCBNetQosFlowControl { 9 | [DscProperty(Mandatory)] 10 | [Ensure] $Ensure 11 | 12 | [DscProperty(Key)] 13 | [ValidateRange(0,7)] 14 | [int] $Priority 15 | 16 | [DscProperty(NotConfigurable)] 17 | [Boolean] $Enabled 18 | 19 | [DCBNetQosFlowControl] Get() { 20 | $FlowControlPriority = Get-NetQosFlowControl -Priority $this.Priority 21 | 22 | $this.Enabled = $FlowControlPriority.Enabled 23 | $this.Priority = $FlowControlPriority.Priority 24 | 25 | return $this 26 | } 27 | 28 | [bool] Test() { 29 | $FlowControlPriority = Get-NetQosFlowControl -Priority $this.Priority 30 | 31 | $testState = $false 32 | if ($this.Ensure -eq [Ensure]::Present) { 33 | Switch ($FlowControlPriority.Enabled) { 34 | $true {$testState = $true} 35 | $false {$testState = $false} 36 | } 37 | } 38 | elseif ($this.Ensure -eq [Ensure]::Absent) { 39 | Switch ($FlowControlPriority.Enabled) { 40 | $true {$testState = $false} 41 | $false {$testState = $true} 42 | } 43 | } 44 | 45 | Return $testState 46 | } 47 | 48 | [Void] Set() { 49 | if ($this.Ensure -eq [Ensure]::Present) { 50 | Write-Verbose "Enabling priority $($this.Priority)" 51 | Set-NetQosFlowControl -Priority $this.Priority -Enabled $true 52 | Write-Verbose "Priority $($this.Priority) is now Enabled" 53 | } 54 | elseif ($this.Ensure -eq [Ensure]::Absent) { 55 | Write-Verbose "Enabling priority $($this.Priority)" 56 | Set-NetQosFlowControl -Priority $this.Priority -Enabled $false 57 | Write-Verbose "Priority $($this.Priority) is now disabled" 58 | } 59 | } 60 | } 61 | 62 | [DscResource()] 63 | Class DCBNetAdapterQos { 64 | [DscProperty(Mandatory)] 65 | [Ensure] $Ensure 66 | 67 | [DscProperty(Key)] 68 | [string] $InterfaceName 69 | 70 | [DscProperty(NotConfigurable)] 71 | [Boolean] $Enabled 72 | 73 | [DCBNetAdapterQos] Get() { 74 | $NetAdapterQosState = Get-NetAdapterQos -Name $this.InterfaceName 75 | 76 | $this.InterfaceName = $NetAdapterQosState.Name 77 | $this.Enabled = $NetAdapterQosState.Enabled 78 | 79 | return $this 80 | } 81 | 82 | [bool]Test() { 83 | $NetAdapterQosState = Get-NetAdapterQos -Name $this.InterfaceName 84 | 85 | $testState = $false 86 | 87 | if ($this.Ensure -eq [Ensure]::Present) { 88 | Switch ($NetAdapterQosState.Enabled) { 89 | $true {$testState = $true} 90 | $false {$testState = $false} 91 | } 92 | } 93 | elseif ($this.Ensure -eq [Ensure]::Absent) { 94 | Switch ($NetAdapterQosState.Enabled) { 95 | $true {$testState = $false} 96 | $false {$testState = $true} 97 | } 98 | } 99 | 100 | Return $testState 101 | } 102 | 103 | [Void] Set() { 104 | if ($this.Ensure -eq [Ensure]::Present) { 105 | Write-Verbose "Enabling QoS on $($this.InterfaceName)" 106 | Set-NetAdapterQos -Name $this.InterfaceName -Enabled $true 107 | Write-Verbose "QoS is now enabled on $($this.InterfaceName)" 108 | } 109 | elseif ($this.Ensure -eq [Ensure]::Absent) { 110 | Write-Verbose "Disabling QoS on $($this.InterfaceName)" 111 | Set-NetAdapterQos -Name $this.InterfaceName -Enabled $false 112 | Write-Verbose "QoS is now disabled on $($this.InterfaceName)" 113 | } 114 | } 115 | } 116 | 117 | [DscResource()] 118 | Class DCBNetQosDcbxSetting { 119 | [DscProperty(Key)] 120 | [Ensure] $Ensure 121 | 122 | [DCBNetQosDcbxSetting] Get() { 123 | $NetQosDcbx = Get-NetQosDcbxSetting 124 | 125 | if ($this.Ensure) { 126 | $this.Willing = $NetQosDcbx.Willing 127 | } 128 | 129 | return $this 130 | } 131 | 132 | [bool]Test() { 133 | $NetQosDcbx = Get-NetQosDcbxSetting 134 | 135 | $testState = $false 136 | 137 | if ($this.Ensure -eq [Ensure]::Present) { 138 | Switch ($NetQosDcbx.Willing) { 139 | $true {$testState = $true} 140 | $false {$testState = $false} 141 | } 142 | } 143 | elseif ($this.Ensure -eq [Ensure]::Absent) { 144 | Switch ($NetQosDcbx.Willing) { 145 | $true {$testState = $false} 146 | $false {$testState = $true} 147 | } 148 | } 149 | 150 | Return $testState 151 | } 152 | 153 | [Void] Set() { 154 | if ($this.Ensure -eq [Ensure]::Present) { 155 | Write-Verbose "Enabling DCBX Willing bit" 156 | Write-Verbose "Note: DCBX is not supported on Windows Server 2016 or Windows Server 2019" 157 | Set-NetQosDcbxSetting -Willing $true 158 | Write-Verbose "DCBX Willing bit is now enabled" 159 | Write-Verbose "Note: DCBX is not supported on Windows Server 2016 or Windows Server 2019" 160 | } 161 | elseif ($this.Ensure -eq [Ensure]::Absent) { 162 | Write-Verbose "Disabling DCBX Willing bit" 163 | Set-NetQosDcbxSetting -Willing $false 164 | Write-Verbose "DCBX Willing bit is now disabled" 165 | } 166 | } 167 | } 168 | 169 | [DscResource()] 170 | Class DCBNetQosPolicy { 171 | [DscProperty(Mandatory)] 172 | [Ensure] $Ensure 173 | 174 | [DscProperty(Key)] 175 | [string] $Name 176 | 177 | [DscProperty(Mandatory)] 178 | [string] $PriorityValue8021Action 179 | 180 | [DscProperty()] 181 | [ValidateSet('Default', 'SMB', 'Cluster', 'LiveMigration')] 182 | [string] $Template 183 | 184 | [DscProperty()] 185 | [string] $NetDirectPortMatchCondition = 0 186 | 187 | [DCBNetQosPolicy] Get() { 188 | $NetQosPolicy = Get-NetQosPolicy -Name $this.Name -ErrorAction SilentlyContinue 189 | 190 | $this.Name = $NetQosPolicy.Name 191 | $this.PriorityValue8021Action = $NetQosPolicy.PriorityValue8021Action 192 | 193 | if ($NetQosPolicy.Template -ne 'None') { $this.Template = $NetQosPolicy.Template } 194 | 195 | if ($NetQosPolicy.NetDirectPortMatchCondition -ne '0') { 196 | $this.NetDirectPortMatchCondition = $NetQosPolicy.NetDirectPortMatchCondition 197 | } 198 | 199 | return $this 200 | } 201 | 202 | [bool] Test() { 203 | $NetQosPolicy = Get-NetQosPolicy -Name $this.Name -ErrorAction SilentlyContinue 204 | 205 | $testState = $false 206 | 207 | If ($NetQosPolicy) { 208 | Switch ($this.Ensure) { 209 | 'Present' { 210 | $teststate = $true 211 | 212 | If ($NetQosPolicy.PriorityValue8021Action -ne $this.PriorityValue8021Action) { $teststate = $false } 213 | 214 | If ($this.Template) { 215 | If ($NetQosPolicy.Template -ne $this.Template) { $teststate = $false } 216 | } 217 | 218 | If ($this.NetDirectPortMatchCondition) { 219 | If ($NetQosPolicy.NetDirectPortMatchCondition -ne $this.NetDirectPortMatchCondition) { $teststate = $false } 220 | } 221 | } 222 | 223 | 'Absent' { $teststate = $false } 224 | } 225 | } else { 226 | Switch ($this.Ensure) { 227 | 'Present' { $teststate = $false } 228 | 'Absent' { $teststate = $true } 229 | } 230 | } 231 | 232 | Return $testState 233 | } 234 | 235 | [Void] Set() { 236 | $NetQosPolicy = Get-NetQosPolicy -Name $this.Name -ErrorAction SilentlyContinue 237 | 238 | If ($NetQosPolicy) { 239 | Switch ($this.Ensure) { 240 | 'Present' { 241 | If ($this.PriorityValue8021Action) { 242 | If ($NetQosPolicy.PriorityValue8021Action -ne $this.PriorityValue8021Action) { 243 | Write-Verbose "Correcting the priority value of NetQosPolicy $($this.Name) to $($this.PriorityValue8021Action)" 244 | Set-NetQosPolicy -Name $this.Name -PriorityValue8021Action $this.PriorityValue8021Action 245 | Write-Verbose "Corrected the priority value of NetQosPolicy $($this.Name) to $($this.PriorityValue8021Action)" 246 | } 247 | } 248 | 249 | if ($this.NetDirectPortMatchCondition) { 250 | If ($NetQosPolicy.NetDirectPortMatchCondition -ne $this.NetDirectPortMatchCondition) { 251 | Write-Verbose "Correcting the NetDirectPortMatchCondition value of NetQosPolicy $($this.Name) to $($this.NetDirectPortMatchCondition)" 252 | Set-NetQosPolicy -Name $this.Name -NetDirectPortMatchCondition $this.NetDirectPortMatchCondition 253 | Write-Verbose "Corrected the NetDirectPortMatchCondition value of NetQosPolicy $($this.Name) to $($this.NetDirectPortMatchCondition)" 254 | } 255 | } 256 | 257 | If ($this.Template) { 258 | If ($NetQosPolicy.Template -ne $this.Template) { 259 | Write-Verbose "Correcting the Template value of NetQosPolicy $($this.Name) to $($this.Template)" 260 | $templateParam = @{ $this.Template = $true } 261 | Set-NetQosPolicy -Name $this.Name @templateParam 262 | Write-Verbose "Corrected the Template value of NetQosPolicy $($this.Name) to $($this.Template)" 263 | } 264 | } 265 | } 266 | 267 | 'Absent' { 268 | Write-Verbose "Removing NetQosPolicy $($this.Name)" 269 | Remove-NetQosPolicy -Name $this.Name 270 | Write-Verbose "NetQosPolicy $($this.Name) has been removed" 271 | } 272 | } 273 | } else { 274 | if ($this.Ensure -eq [Ensure]::Present) { 275 | if ($this.NetDirectPortMatchCondition -ne 0) { 276 | Write-Verbose "Creating NetQosPolicy $($this.Name)" 277 | New-NetQosPolicy -Name $this.Name -PriorityValue8021Action $this.PriorityValue8021Action -NetDirectPortMatchCondition $this.NetDirectPortMatchCondition 278 | Write-Verbose "NetQosPolicy $($this.Name) has been created" 279 | } 280 | elseif ($this.Template -ne 'None') { 281 | Write-Verbose "Creating NetQosPolicy $($this.Name)" 282 | $templateParam = @{ $this.Template = $true } 283 | New-NetQosPolicy -Name $this.Name -PriorityValue8021Action $this.PriorityValue8021Action @templateParam 284 | Write-Verbose "NetQosPolicy $($this.Name) has been created" 285 | } 286 | else { Write-Verbose 'Catastrophic Failure' } 287 | } 288 | } 289 | } 290 | } 291 | 292 | [DscResource()] 293 | Class DCBNetQosTrafficClass { 294 | [DscProperty(Mandatory)] 295 | [Ensure] $Ensure 296 | 297 | [DscProperty(Key)] 298 | [string] $Name 299 | 300 | [DscProperty(Mandatory)] 301 | [string] $Priority 302 | 303 | [DscProperty(Mandatory)] 304 | [ValidateRange(1,99)] 305 | [string] $BandwidthPercentage 306 | 307 | [DscProperty(Mandatory)] 308 | [ValidateSet('ETS','Strict')] 309 | [string] $Algorithm = 'ETS' 310 | 311 | [DCBNetQosTrafficClass] Get() { 312 | $NetQosTrafficClass = Get-NetQosTrafficClass -Name $this.Name -ErrorAction SilentlyContinue 313 | 314 | $this.Name = $NetQosTrafficClass.Name 315 | $this.Priority = $NetQosTrafficClass.Priority 316 | $this.BandwidthPercentage = $NetQosTrafficClass.BandwidthPercentage 317 | $this.Algorithm = $NetQosTrafficClass.Algorithm 318 | 319 | return $this 320 | } 321 | 322 | [bool] Test() { 323 | $NetQosTrafficClass = Get-NetQosTrafficClass -Name $this.Name -ErrorAction SilentlyContinue 324 | 325 | $testState = $false 326 | 327 | If ($NetQosTrafficClass) { 328 | Switch ($this.Ensure.ToString()) { 329 | 'Present' { 330 | $teststate = $true 331 | If ($NetQosTrafficClass.Priority -ne $this.Priority) { $teststate = $false } 332 | If ($NetQosTrafficClass.BandwidthPercentage -ne $this.BandwidthPercentage) { $teststate = $false } 333 | If ($NetQosTrafficClass.Algorithm -ne $this.Algorithm) { $teststate = $false } 334 | } 335 | 336 | 'Absent' { $teststate = $false } 337 | } 338 | } else { 339 | Switch ($this.Ensure) { 340 | 'Present' { $teststate = $false } 341 | 'Absent' { $teststate = $true } 342 | } 343 | } 344 | 345 | Return $testState 346 | } 347 | 348 | [Void] Set() { 349 | $NetQosTrafficClass = Get-NetQosTrafficClass -Name $this.Name -ErrorAction SilentlyContinue 350 | 351 | If ($NetQosTrafficClass) { 352 | Switch ($this.Ensure) { 353 | 'Present' { 354 | If ($NetQosTrafficClass.Priority -ne $this.Priority) { 355 | Write-Verbose "Correcting the priority value of NetQosTrafficClass $($this.Name) to $($this.Priority)" 356 | Set-NetQosTrafficClass -Name $this.Name -Priority $this.Priority 357 | Write-Verbose "Corrected the priority value of NetQosTrafficClass $($this.Name) to $($this.Priority)" 358 | } 359 | 360 | If ($NetQosTrafficClass.BandwidthPercentage -ne $this.BandwidthPercentage) { 361 | Write-Verbose "Correcting the BandwidthPercentage value of NetQosTrafficClass $($this.Name) to $($this.BandwidthPercentage)" 362 | Set-NetQosTrafficClass -Name $this.Name -BandwidthPercentage $this.BandwidthPercentage 363 | Write-Verbose "Corrected the BandwidthPercentage value of NetQosTrafficClass $($this.Name) to $($this.BandwidthPercentage)" 364 | } 365 | 366 | If ($NetQosTrafficClass.Algorithm -ne $this.Algorithm) { 367 | Write-Verbose "Correcting the Algorithm value of NetQosTrafficClass $($this.Name) to $($this.Algorithm)" 368 | Set-NetQosTrafficClass -Name $this.Name -Algorithm $this.Algorithm 369 | Write-Verbose "Corrected the Template value of NetQosTrafficClass $($this.Name) to $($this.Algorithm)" 370 | } 371 | } 372 | 373 | 'Absent' { 374 | Write-Verbose "Removing NetQosTrafficClass $($this.Name)" 375 | Remove-NetQosTrafficClass -Name $this.Name 376 | Write-Verbose "NetQosTrafficClass $($this.Name) has been removed" 377 | } 378 | } 379 | } else { 380 | if ($this.Ensure -eq [Ensure]::Present) { 381 | Write-Verbose "Creating NetQosTrafficClass $($this.Name)" 382 | New-NetQosTrafficClass -Name $this.Name -Priority $this.Priority -BandwidthPercentage $this.BandwidthPercentage -Algorithm $this.Algorithm 383 | Write-Verbose "NetQosTrafficClass $($this.Name) has been created" 384 | } 385 | } 386 | } 387 | } 388 | #endregion DCB DSC Resources 389 | 390 | #region FabricInfo 391 | #region Helper Functions (Not Exported) 392 | Function Get-Interfaces { 393 | param ( 394 | [Parameter(Mandatory=$false)] 395 | [String[]] $InterfaceNames, 396 | 397 | [Parameter(Mandatory=$false)] 398 | [String[]] $InterfaceIndex, 399 | 400 | [Parameter(Mandatory=$false)] 401 | [String] $SwitchName 402 | ) 403 | 404 | If ($SwitchName) { 405 | $VMSwitchTeam = Get-VMSwitchTeam -Name $SwitchName 406 | $Interfaces = Get-NetAdapter -InterfaceDescription $VMSwitchTeam.NetAdapterInterfaceDescription 407 | } 408 | elseif ($InterfaceNames) { $Interfaces = Get-NetAdapter -Name $InterfaceNames } 409 | elseif ($InterfaceIndex) { $Interfaces = Get-NetAdapter -InterfaceIndex $InterfaceIndex } 410 | 411 | Return $Interfaces 412 | } 413 | #region LLDP 414 | Function Invoke-BitShift { 415 | param ( 416 | [Parameter(Mandatory,Position=0)] 417 | [int] $x , 418 | 419 | [Parameter(ParameterSetName='Left')] 420 | [ValidateRange(0,[int]::MaxValue)] 421 | [int] $Left , 422 | 423 | [Parameter(ParameterSetName='Right')] 424 | [ValidateRange(0,[int]::MaxValue)] 425 | [int] $Right 426 | ) 427 | 428 | $shift = If($PSCmdlet.ParameterSetName -eq 'Left') { $Left } 429 | Else { -$Right } 430 | 431 | Return [math]::Floor($x * [math]::Pow(2,$shift)) 432 | } 433 | 434 | Function Get-LLDPEvents 435 | { 436 | param ($RemainingIndexes) 437 | 438 | $EventPerInterface = @() 439 | :enoughEvents while ($Null -ne $remainingIndexes) { 440 | #$CuratedEvents = Get-WinEvent -LogName Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic -Oldest -ErrorAction SilentlyContinue | 441 | # Where-Object { $_.ID -eq 10041 -and $_.TimeCreated -ge (Get-Date).AddMinutes(-5)} | Sort-Object TimeCreated -Descending 442 | 443 | # search for matches using the appropriate filter 444 | [hashtable]$eventFilter = @{LogName='Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic'; ID=10041; StartTime=$(Get-Date).AddMinutes(-5)} 445 | 446 | [array]$CuratedEvents = Get-WinEvent -FilterHashtable $eventFilter -Oldest -EA SilentlyContinue | Sort-Object TimeCreated -Descending 447 | 448 | foreach ($thisEvent in $CuratedEvents) 449 | { 450 | #$thisEvent = $_ 451 | 452 | if ($remainingIndexes -contains $thisEvent.Properties[0].Value) 453 | { 454 | $remainingIndexes = $remainingIndexes | Where-Object { $_ -ne $thisEvent.Properties[0].Value } 455 | $EventPerInterface += $thisEvent 456 | } 457 | 458 | # Note: We only want one event per index, so we'll break here if we received enough events 459 | if ($null -eq $remainingIndexes) {break enoughEvents} 460 | } 461 | 462 | break enoughEvents 463 | } 464 | 465 | if ($Null -ne $RemainingIndexes) { $global:IndexesMissingEvents = $RemainingIndexes } 466 | 467 | return $EventPerInterface 468 | } 469 | 470 | 471 | function Convert-Bytes2IP 472 | { 473 | [CmdletBinding()] 474 | param ( 475 | [Parameter()] 476 | [byte[]] 477 | $ipBytes 478 | ) 479 | 480 | # get them bits 481 | $IPBinary = (([System.BitConverter]::ToString($ipBytes).Replace('-','')).ToCharArray() | & { process { [System.Convert]::ToString([byte]"0x$_",2).PadLeft(4,'0') } }) -join '' 482 | 483 | try 484 | { 485 | $IP = ([System.Net.IPAddress]"$([System.Convert]::ToInt64($IPBinary,2))").IPAddressToString 486 | } 487 | catch 488 | { 489 | Write-Warning "Convert-Bytes2IP - Failed to convert bytes to IP address: $_" 490 | return $null 491 | } 492 | 493 | return $IP 494 | } 495 | 496 | 497 | # https://www.ieee802.org/1/files/public/docs2002/LLDP%20Overview.pdf 498 | 499 | function Get-LldpTlv 500 | { 501 | [CmdletBinding()] 502 | param ( 503 | [Parameter()] 504 | [byte[]] 505 | $tlvBytes 506 | ) 507 | 508 | Write-Verbose "Get-LldpTlv - Begin" 509 | Write-Verbose "Get-LldpTlv - Bytes: $($tlvBytes -join ", ")" 510 | 511 | # skip processing if we hit the end 512 | if ($tlvBytes[0] -eq 0) 513 | { 514 | return ([PSCustomObject]@{ 515 | Type = 0 516 | Len = 0 517 | }) 518 | } 519 | 520 | # get them bits 521 | $rawBits = (([System.BitConverter]::ToString($tlvBytes).Replace('-','')).ToCharArray() | & { process { [System.Convert]::ToString([byte]"0x$_",2).PadLeft(4,'0') } }) -join '' 522 | 523 | Write-Verbose "Get-LldpTlv - bits: $rawBits" 524 | 525 | $tlvType = [convert]::ToInt32($rawBits.Substring(0,7), 2) 526 | Write-Verbose "Get-LldpTlv - Type: $tlvType" 527 | 528 | $tlvLen = [convert]::ToInt32($rawBits.Substring(7,9), 2) 529 | Write-Verbose "Get-LldpTlv - Length: $tlvLen" 530 | 531 | Write-Verbose "Get-LldpTlv - End" 532 | return ([PSCustomObject]@{ 533 | Type = $tlvType 534 | Len = $tlvLen 535 | }) 536 | } 537 | 538 | 539 | function Get-TlvChassisId 540 | { 541 | [CmdletBinding()] 542 | param ( 543 | [Parameter()] 544 | [byte[]] 545 | $chasBytes 546 | ) 547 | 548 | # get the Chassis ID Subtype 549 | [int]$chasIdType = "0x$($chasBytes[0])" 550 | 551 | switch -Regex ($chasIdType) 552 | { 553 | "[1-3]" 554 | { 555 | # convert bytes to string 556 | return ( [System.Text.Encoding]::ASCII.GetString($chasBytes[1..($chasBytes.Length - 1)]) ) 557 | } 558 | "4" 559 | { 560 | # MAC address 561 | return ("{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $chasBytes[1..($chasBytes.Length - 1)]) 562 | } 563 | 564 | "5" 565 | { 566 | # management address 567 | return (Convert-Bytes2IP ($chasBytes[1..($chasBytes.Length - 1)]) ) 568 | } 569 | 570 | Default 571 | { 572 | Write-Warning "Unknown Chassis ID type: $chasIdType" 573 | return "Unknown" 574 | } 575 | } 576 | } 577 | 578 | 579 | function Get-TlvPortId 580 | { 581 | [CmdletBinding()] 582 | param ( 583 | [Parameter()] 584 | [byte[]] 585 | $portBytes 586 | ) 587 | 588 | # get the Chassis ID Subtype 589 | [int]$portIdType = "0x$($portBytes[0])" 590 | 591 | Write-Verbose "Get-TlvPortId - Port Type: $portIdType" 592 | 593 | switch -Regex ($portIdType) 594 | { 595 | "[1-2]|[5]" 596 | { 597 | # convert bytes to string 598 | return ( [System.Text.Encoding]::ASCII.GetString($portBytes[1..($portBytes.Length - 1)]) ) 599 | } 600 | "3" 601 | { 602 | # MAC address 603 | return ("{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $portBytes[1..($portBytes.Length - 1)]) 604 | } 605 | 606 | "4" 607 | { 608 | # management address 609 | return (Convert-Bytes2IP ($portBytes[1..($portBytes.Length - 1)]) ) 610 | } 611 | 612 | Default 613 | { 614 | Write-Warning "Unknown Port ID type: $portIdType" 615 | return "Unknown" 616 | } 617 | } 618 | } 619 | 620 | 621 | function Get-LldpPfc 622 | { 623 | [CmdletBinding()] 624 | param ( 625 | [Parameter()] 626 | [byte[]] 627 | $portBytes 628 | ) 629 | 630 | $rawBits = (([System.BitConverter]::ToString($portBytes).Replace('-','')).ToCharArray() | & { process { [System.Convert]::ToString([byte]"0x$_",2).PadLeft(4,'0') } }) -join '' 631 | 632 | # PFC flags to Int 633 | $intPFC = [convert]::ToInt32($rawBits.Substring(8), 2) 634 | 635 | # only return Priorities for now 636 | return ([enum]::GetValues([PFC_Priorities]) | Where-Object {$_.value__ -band $intPFC} | ForEach-Object {$_.ToString()}) 637 | 638 | } 639 | 640 | 641 | function Test-LbfoEnabled 642 | { 643 | param ( 644 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceNames', Position=0)] 645 | [String[]] $InterfaceNames, 646 | 647 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceIndex')] 648 | [UInt32[]] $InterfaceIndex, 649 | 650 | [Parameter(Mandatory=$true, ParameterSetName = 'SwitchName')] 651 | [String] $SwitchName 652 | ) 653 | 654 | # LBFO detection 655 | [array]$isLbfoUsed = Get-NetLbfoTeam -EA SilentlyContinue 656 | if ($isLbfoUsed) 657 | { 658 | # throw a warning when the interface or switch is using LBFO 659 | if ($PSBoundParameters.ContainsKey('SwitchName')) 660 | { 661 | # skip this check if the switch is a teamed interface (SET) 662 | $vSwitch = Get-VMSwitch -SwitchName $SwitchName -EA SilentlyContinue 663 | 664 | if (-NOT $vSwitch) 665 | { 666 | return (Write-Error "No switch found named $SwitchName." -EA Stop) 667 | } 668 | elseif ($vSwitch.EmbeddedTeamingEnabled -eq $false) 669 | { 670 | $switchAdapterDesc = $vSwitch | ForEach-Object { $_.NetAdapterInterfaceDescription } 671 | $switchAdapterName = Get-NetAdapter -InterfaceDescription $switchAdapterDesc -EA SilentlyContinue | ForEach-Object Name 672 | 673 | if ($isLbfoUsed.Name -contains $switchAdapterName) 674 | { 675 | $LbfoUsed = Get-NetAdapter -InterfaceAlias $switchAdapterName 676 | } 677 | } 678 | } 679 | elseif ($PSBoundParameters.ContainsKey('InterfaceNames')) 680 | { 681 | [String[]]$lbfoTeamMem = Get-NetLbfoTeamMember -EA SilentlyContinue | ForEach-Object Name 682 | 683 | $LbfoUsed = $InterfaceNames | ForEach-Object { if ($lbfoTeamMem -contains $_) { $_ } } 684 | } 685 | elseif ($PSBoundParameters.ContainsKey('InterfaceIndex')) 686 | { 687 | [String[]]$lbfoTeamMem = Get-NetLbfoTeamMember -EA SilentlyContinue | ForEach-Object Name 688 | 689 | [string[]]$InterfaceNames = Get-NetAdapter -InterfaceIndex $InterfaceIndex -EA SilentlyContinue | ForEach-Object Name 690 | 691 | $LbfoUsed = $InterfaceNames | ForEach-Object { if ($lbfoTeamMem -contains $_) { $_ } } 692 | } 693 | } 694 | 695 | if ($LbfoUsed) 696 | { 697 | Write-Warning "LBFO Teams are not supported. DataCenterBrinding cmdlets may not operate correctly or provide inaccurate results. Microsoft recommends using Switch Embedded Teaming (SET): https://aka.ms/DownWithLBFO" 698 | return $true 699 | } 700 | 701 | # no LBFO detected 702 | return $false 703 | } 704 | 705 | 706 | 707 | function Parse-LLDPPacket { 708 | param ($Events) 709 | 710 | $Table = @() 711 | 712 | [hashtable]$tlvTable = [ordered]@{ 713 | End = 0 714 | ChassisId = 1 715 | PortId = 2 716 | TimeToLive = 3 717 | PortDescription = 4 718 | SystemName = 5 719 | SystemDesc = 6 720 | OrganizationSpecific = 127 721 | } 722 | 723 | [Flags()] enum PFC_Priorities { 724 | Priority0 = 1 725 | Priority1 = 2 726 | Priority2 = 4 727 | Priority3 = 8 728 | Priority4 = 16 729 | Priority5 = 32 730 | Priority6 = 64 731 | Priority7 = 128 732 | } 733 | 734 | foreach ($thisEvent in $Events) 735 | { 736 | $bytes = $thisEvent.Properties[3].Value 737 | 738 | # the LLDP header always starts at offset 14 739 | $offset = 14 740 | 741 | # keeps track of VLAN IDs 742 | [int[]]$VLANID = @() 743 | 744 | # parse the ethernet header 745 | $ethDst = "{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $bytes[0..5] 746 | $ethSrc = "{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $bytes[6..11] 747 | $ethType = "0x$([BitConverter]::ToString($bytes[12]) + [BitConverter]::ToString($bytes[13]))" 748 | 749 | 750 | ## parse the LLDP header ## 751 | 752 | # parse until the "End of LLDPDU" TLV is reached (00 00) 753 | $EndOfLLDPDU = $false 754 | 755 | while ($EndOfLLDPDU -eq $false) 756 | { 757 | # parse the TLV 758 | Write-Verbose "tlv header: $($bytes[$offset..($offset+1)] -join ", ")" 759 | $TLV = Get-LldpTlv $bytes[$offset..($offset+1)] 760 | 761 | # offset + 2 (TLV bytes) + TLV Length - 1 (to compensate for index starting at 0) 762 | $tlvEnd = $offset + 2 + $TLV.Len - 1 763 | 764 | switch ($TLV.Type) 765 | { 766 | #region 767 | $tlvTable.End 768 | { 769 | $EndOfLLDPDU = $true 770 | break 771 | } 772 | 773 | $tlvTable.ChassisId 774 | { 775 | # just need the string Chassis ID back 776 | $ChassisID = Get-TlvChassisId $bytes[($offset+2)..$tlvEnd] 777 | break 778 | } 779 | 780 | $tlvTable.PortId 781 | { 782 | $PortId = Get-TlvPortId $bytes[($offset+2)..$tlvEnd] 783 | break 784 | } 785 | 786 | <# 787 | $tlvTable.TimeToLive 788 | { 789 | [int]$tlvTTL = "0x$(($bytes[($offset+2)..$tlvEnd] | ForEach-Object { "{0:X2}" -f $_ }) -join '')" 790 | break 791 | } 792 | #> 793 | 794 | $tlvTable.PortDescription 795 | { 796 | $PortDescription = [System.Text.Encoding]::ASCII.GetString($bytes[($offset+2)..$tlvEnd]) 797 | break 798 | } 799 | 800 | $tlvTable.SystemName 801 | { 802 | $SystemName = [System.Text.Encoding]::ASCII.GetString($bytes[($offset+2)..$tlvEnd]) 803 | break 804 | } 805 | 806 | $tlvTable.SystemDesc 807 | { 808 | $SystemDesc = [System.Text.Encoding]::ASCII.GetString($bytes[($offset+2)..$tlvEnd]) 809 | break 810 | } 811 | #endregion 812 | 813 | $tlvTable.OrganizationSpecific 814 | { 815 | <# 816 | We only care about the following org codes and subtypes: 817 | 818 | Code 00:12:0f (IEEE 802.3) - Subtype 0x04 (MTU) 819 | Code 00:80:c2 (IEEE) - Subtype 0x0b (PFC) 820 | Code 00:80:c2 (IEEE) - Subtype 0x01 (Default VLAN) 821 | Code 00:80:c2 (IEEE) - Subtype 0x03 (Port VLANs|VLAN Name) ---> There can be multiple entries for this subtype, one for each advertised VLAN. 822 | 823 | #> 824 | # org code 825 | $orgCode = "{0:X2}:{1:X2}:{2:X2}" -f $bytes[($offset+2)..($offset+4)] 826 | 827 | switch ($orgCode) 828 | { 829 | "00:80:C2" 830 | { 831 | # Get the subtype 832 | $subtype = $bytes[($offset+5)] 833 | 834 | switch ($subtype) 835 | { 836 | 1 837 | { 838 | # Port VLAN ID (default) - can be up to 2 bytes (4096) in length 839 | #$NativeVLAN = Convert-Bytes2Int $bytes[($offset+6)..$tlvEnd] 840 | $NativeVLAN = [convert]::ToInt32( (($bytes[($offset+6)..$tlvEnd] | & { process { [System.Convert]::ToString($_,2).PadLeft(8,'0') } } ) -join ''), 2) 841 | break 842 | } 843 | 844 | 3 845 | { 846 | # VLAN ID = first 2 bytes after the subtype 847 | #$VLANID += Convert-Bytes2Int $bytes[($offset+6)..($offset+7)] 848 | $VLANID += [convert]::ToInt32( (($bytes[($offset+6)..($offset+7)] | & { process { [System.Convert]::ToString($_,2).PadLeft(8,'0') } } ) -join ''), 2) 849 | 850 | # 1 byte for the length of the name 851 | # but first make sure we aren't at the end of the TLV by checking if the last byte of the VLAN ID is less than tlvEnd 852 | # we don't care about the VLAN Name right now, but I'll leave this code in here in case we do in the future. 853 | <# 854 | if ( ($offset+7) -lt $tlvEnd ) 855 | { 856 | # ($offset+8) thru $tlvEnd should be the string VLAN name. Skipping additional checks for speed. 857 | $vlanName = [System.Text.Encoding]::ASCII.GetString($bytes[($offset+8)..$tlvEnd]) 858 | } 859 | #> 860 | 861 | break 862 | } 863 | 864 | 11 865 | { 866 | # get PFC stuff 867 | $PFC = Get-LldpPfc $bytes[($offset+6)..$tlvEnd] 868 | } 869 | 870 | default 871 | { 872 | Write-Verbose "Ignoring Organization Unique Code 00:80:C2 (IEEE), subtype $subType." 873 | break 874 | } 875 | } 876 | break 877 | } 878 | 879 | "00:12:0f" 880 | { 881 | # Get the subtype 882 | $subtype = $bytes[($offset+5)] 883 | 884 | 885 | switch ($subtype) 886 | { 887 | 4 888 | { 889 | # frame size (MTU) 890 | #$FrameSize = Convert-Bytes2Int $bytes[($offset+6)..($offset+7)] 891 | $FrameSize = [convert]::ToInt32( (($bytes[($offset+6)..($offset+7)] | & { process { [System.Convert]::ToString($_,2).PadLeft(8,'0') } } ) -join ''), 2) 892 | } 893 | 894 | default 895 | { 896 | Write-Verbose "Ignoring Organization Unique Code 00:12:0f (IEEE 802.3), subtype $subType." 897 | break 898 | } 899 | } 900 | break 901 | } 902 | 903 | default 904 | { 905 | Write-Verbose "Ignoring Organization Unique Code $orgCode." 906 | break 907 | } 908 | } 909 | } 910 | 911 | default 912 | { 913 | $knownTlv = ($tlvTable.GetEnumerator() | Where-Object Value -eq $TLV.Type) 914 | if ($knownTlv) { 915 | $tlvName = $knownTlv.Name 916 | } 917 | 918 | Write-Verbose "Ignoring TLV $($TLV.Type)$( if ($tlvName) { " ($tlvName)" })." 919 | Remove-Variable tlvName, knownTlv -EA SilentlyContinue 920 | break 921 | } 922 | } 923 | 924 | # increment offset to the start of the next TLV 925 | $offset = $tlvEnd + 1 926 | } 927 | 928 | 929 | # Set defaults in case the switch doesn't provide the information and guide the customer in their troubleshooting 930 | if (-not($PortDescription)) { $PortDescription = 'Information Not Provided By Switch' } 931 | if (-not($PortId)) { $PortId = 'Information Not Provided By Switch' } 932 | if (-not($SystemName)) { $SystemName = 'Information Not Provided By Switch' } 933 | if (-not($SystemDesc)) { $SystemDesc = 'Information Not Provided By Switch' } 934 | if (-not($NativeVLAN)) { $NativeVLAN = 'Information Not Provided By Switch' } 935 | if (-not($VLANID)) { [string]$VLANID = 'Information Not Provided By Switch' } 936 | if (-not($FrameSize)) { $FrameSize = 'Information Not Provided By Switch' } 937 | if (-not($PFC)) { $PFC = 'Information Not Provided By Switch' } 938 | 939 | $Table += [ordered] @{ 940 | InterfaceName = (Get-NetAdapter -InterfaceIndex $thisEvent.Properties[0].Value).Name 941 | InterfaceIndex = $thisEvent.Properties[0].Value 942 | DateTime = $thisEvent.TimeCreated 943 | 944 | Destination = $ethDst # Mandatory 945 | sourceMac = $ethSrc # Mandatory 946 | EtherType = $ethType # Mandatory 947 | ChassisID = $ChassisID # Mandatory 948 | PortID = $PortId # Mandatory 949 | 950 | PortDescription = $PortDescription # Optional 951 | SystemName = $SystemName # Optional 952 | SystemDesc = $SystemDesc # Optional 953 | 954 | 955 | NativeVLAN = $NativeVLAN # IEEE 802.1 TLV:127 Subtype:1 956 | VLANID = $VLANID # IEEE 802.1 TLV:127 Subtype:3 957 | FrameSize = $FrameSize # IEEE 802.3 TLV:127 Subtype:4 958 | PFC = $PFC | Sort-Object # IEEE 802.1 TLV:127 Subtype:11 959 | 960 | Bytes = $bytes # Raw Data from Packet 961 | } 962 | 963 | # clean up to prevent bad output 964 | Remove-Variable thisEvent, ethDst, ethSrc, ethType, ChassisID, PortId, PortDescription, SystemName, SystemDesc, NativeVLAN, VLANID, FrameSize, PFC, bytes -EA SilentlyContinue 965 | } 966 | 967 | 968 | return $Table 969 | } 970 | 971 | <# 972 | Function Parse-LLDPPacket { 973 | param ($Events) 974 | 975 | $Table = @() 976 | 977 | $tlv = @{ 978 | ChassisId = 1 979 | PortId = 2 980 | TimeToLive = 3 981 | PortDescription = 4 982 | SystemName = 5 983 | OrganizationSpecific = 127 984 | } 985 | 986 | [Flags()] enum PFC_lower { 987 | Priority0 = 1 988 | Priority1 = 2 989 | Priority2 = 4 990 | Priority3 = 8 991 | } 992 | 993 | [Flags()] enum PFC_higher { 994 | Priority4 = 1 995 | Priority5 = 2 996 | Priority6 = 4 997 | Priority7 = 8 998 | } 999 | 1000 | $Events | ForEach-Object { 1001 | $thisEvent = $_ 1002 | $offset = 14 1003 | $VLANID = @() 1004 | $bytes = $thisEvent.Properties[3].Value 1005 | 1006 | $Destination = "{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $bytes[0..5] 1007 | $SourceMac = "{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $bytes[6..11] 1008 | $EtherType = "0x$([BitConverter]::ToString($bytes[12]) + [BitConverter]::ToString($bytes[13]))" 1009 | 1010 | While ($bytes[$offset] -ne 0) { 1011 | $type = $bytes[$Offset] -shr 1 1012 | $Length = $Length = (Invoke-BitShift ($bytes[$offset] -band 1) -left 8) -bor $bytes[$offset + 1] 1013 | 1014 | Switch ($type) { 1015 | $tlv.ChassisID { 1016 | Switch ($bytes[$offset + 2]) { 1017 | # Mac Address SubType 1018 | 4 { $ChassisID = "{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}" -f $bytes[($offset + 3)..($offset + 8)] } 1019 | } 1020 | } 1021 | 1022 | $tlv.PortDescription { $PortDescription = ([System.Text.Encoding]::ASCII.GetString($bytes[$Offset..($Offset + $Length + 1)])).Trim() } 1023 | $tlv.SystemName { $SystemName = ([System.Text.Encoding]::ASCII.GetString($bytes[$Offset..($Offset + $Length + 1)])).Trim() } 1024 | 1025 | $tlv.OrganizationSpecific { 1026 | $OUI = [System.BitConverter]::ToString($bytes[($Offset+2)..($Offset + 4)]).Replace('-', ':') 1027 | 1028 | Switch ($bytes[$offset + 5]) { 1029 | # Additional Subtypes - https://wiki.wireshark.org/LinkLayerDiscoveryProtocol#:~:text=All%20Organizationally%20Specific%20TLVs%20start%20with%20an%20LLDP,followed%20by%20a%201%20octet%20organizationally%20defined%20subtype 1030 | {$_ -eq '1' -and $OUI -eq '00:80:C2'} { 1031 | $NativeVLAN = (Invoke-BitShift $bytes[$offset + 6] -left 8) -bor $bytes[$offset + 7] 1032 | } 1033 | {$_ -eq '3' -and $OUI -eq '00:80:C2'} { $VLANID += (Invoke-BitShift($bytes[$offset + 6] -band 0xf) -Right 8) -bor $bytes[$offset + 7] } 1034 | {$_ -eq '4' -and $OUI -eq '00:12:0f'} { $FrameSize = (Invoke-BitShift $bytes[$offset + 6] -left 8) -bor $bytes[$offset + 7] } 1035 | {$_ -eq '11' -and $OUI -eq '00:80:C2'} { 1036 | # Possible that more than one is enabled, so need to grab all of these 1037 | # Uses exactly 1 byte to define the state of each PFC priority 1038 | # The first 4 bits define upper range of priority e.g. 0001 = Priority 4 Enabled 1039 | # The last 4 bits define lower range of priority e.g. 1000 = Priority 3 Enabled 1040 | 1041 | $thisByte = "{0:D2}" -f $bytes[$offset + 7] 1042 | # HigherBits = the left-most values in the last byte; LowerBits = the right-most values in the last byte 1043 | $HigherBits = -join $thisByte.ToString()[0] # -join operates as a substring 1044 | $LowerBits = -join $thisByte.ToString()[1] # -join operates as a substring 1045 | 1046 | $PFC = @() 1047 | if ($HigherBits -ne 0) { 1048 | $HigherPriority = [enum]::GetValues([PFC_higher]) | Where-Object {$_.value__ -band $HigherBits} 1049 | foreach ($priority in $HigherPriority) { $PFC += $priority.toString() } 1050 | } 1051 | 1052 | if ($LowerBits -ne 0) { 1053 | $LowerPriority = [enum]::GetValues([PFC_lower]) | Where-Object {$_.value__ -band $LowerBits} 1054 | foreach ($priority in $LowerPriority) { $PFC += $priority.toString() } 1055 | } 1056 | } 1057 | } 1058 | } 1059 | } 1060 | 1061 | $offset = $offset + $Length + 2 1062 | } 1063 | 1064 | # Set defaults in case the switch doesn't provide the information and guide the customer in their troubleshooting 1065 | if (-not($PortDescription)) { $PortDescription = 'Information Not Provided By Switch' } 1066 | if (-not($SystemName)) { $SystemName = 'Information Not Provided By Switch' } 1067 | if (-not($NativeVLAN)) { $NativeVLAN = 'Information Not Provided By Switch' } 1068 | if (-not($VLANID)) { $VLANID = 'Information Not Provided By Switch' } 1069 | if (-not($FrameSize)) { $FrameSize = 'Information Not Provided By Switch' } 1070 | if (-not($PFC)) { $PFC = 'Information Not Provided By Switch' } 1071 | 1072 | $Table += [ordered] @{ 1073 | InterfaceName = (Get-NetAdapter -InterfaceIndex $thisEvent.Properties[0].Value).Name 1074 | InterfaceIndex = $thisEvent.Properties[0].Value 1075 | DateTime = $thisEvent.TimeCreated 1076 | 1077 | Destination = $Destination # Mandatory 1078 | sourceMac = $sourceMac # Mandatory 1079 | EtherType = $EtherType # Mandatory 1080 | ChassisID = $ChassisID # Mandatory 1081 | 1082 | PortDescription = $PortDescription # Optional 1083 | SystemName = $SystemName # Optional 1084 | 1085 | NativeVLAN = $NativeVLAN # IEEE 802.1 TLV:127 Subtype:1 1086 | VLANID = $VLANID # IEEE 802.1 TLV:127 Subtype:3 1087 | FrameSize = $FrameSize # IEEE 802.3 TLV:127 Subtype:4 1088 | PFC = $PFC | Sort-Object # IEEE 802.1 TLV:127 Subtype:11 1089 | 1090 | Bytes = $bytes # Raw Data from Packet 1091 | } 1092 | } 1093 | 1094 | Return $Table 1095 | } 1096 | #> 1097 | #endregion LLDP 1098 | 1099 | #region HostMap 1100 | Function Convert-CIDRToMask { 1101 | param ( 1102 | [Parameter(Mandatory = $true)] 1103 | [int] $PrefixLength 1104 | ) 1105 | 1106 | $bitString = ('1' * $prefixLength).PadRight(32, '0') 1107 | 1108 | [String] $MaskString = @() 1109 | 1110 | for($i = 0; $i -lt 32; $i += 8){ 1111 | $byteString = $bitString.Substring($i,8) 1112 | $MaskString += "$([Convert]::ToInt32($byteString, 2))." 1113 | } 1114 | 1115 | Return $MaskString.TrimEnd('.') 1116 | } 1117 | 1118 | Function Convert-MaskToCIDR { 1119 | param ( 1120 | [Parameter(Mandatory = $true)] 1121 | [IPAddress] $SubnetMask 1122 | ) 1123 | 1124 | [String] $binaryString = @() 1125 | $SubnetMask.GetAddressBytes() | ForEach-Object { $binaryString += [Convert]::ToString($_, 2) } 1126 | 1127 | Return $binaryString.TrimEnd('0').Length 1128 | } 1129 | 1130 | Function Convert-IPv4ToInt { 1131 | Param ( 1132 | [Parameter(Mandatory = $true)] 1133 | [IPAddress] $IPv4Address 1134 | ) 1135 | 1136 | $bytes = $IPv4Address.GetAddressBytes() 1137 | 1138 | Return [System.BitConverter]::ToUInt32($bytes,0) 1139 | } 1140 | 1141 | Function Convert-IntToIPv4 { 1142 | Param ( 1143 | [Parameter(Mandatory = $true)] 1144 | [uint32]$Integer 1145 | ) 1146 | 1147 | $bytes = [System.BitConverter]::GetBytes($Integer) 1148 | 1149 | Return ([IPAddress]($bytes)).ToString() 1150 | } 1151 | 1152 | Class InterfaceDetails { 1153 | [String] $IPAddress 1154 | [String] $SubnetMask 1155 | [String] $PrefixLength 1156 | 1157 | [String] $Network 1158 | [String] $Subnet 1159 | [String] $VLAN 1160 | 1161 | [string] $NetAdapterHostVNICName 1162 | [string] $VMNetworkAdapterName 1163 | } 1164 | #endregion Host Map 1165 | 1166 | #endregion Helper Functions 1167 | 1168 | #region Exportable 1169 | function Test-FabricInfo { 1170 | <# 1171 | .SYNOPSIS 1172 | Verifies prerequisites to running the other cmdlets in this module 1173 | 1174 | .EXAMPLE 1175 | Test-FabricInfo 1176 | 1177 | .NOTES 1178 | Author: Windows Core Networking team @ Microsoft 1179 | 1180 | Please file issues on GitHub @ GitHub.com/Microsoft/DataCenterBridging 1181 | 1182 | .LINK 1183 | Windows Networking Blog : https://aka.ms/MSFTNetworkBlog 1184 | #> 1185 | 1186 | [CmdletBinding(DefaultParameterSetName = 'InterfaceNames')] 1187 | param ( 1188 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceNames', Position=0)] 1189 | [String[]] $InterfaceNames, 1190 | 1191 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceIndex')] 1192 | [String[]] $InterfaceIndex, 1193 | 1194 | [Parameter(Mandatory=$true, ParameterSetName = 'SwitchName')] 1195 | [String] $SwitchName, 1196 | 1197 | [Parameter(Mandatory=$false)] 1198 | [Switch] $AdapterStateOnly, 1199 | 1200 | [Parameter(Mandatory=$false)] 1201 | [Switch] $NodeStateOnly 1202 | ) 1203 | 1204 | $Pass = '+' 1205 | $Fail = '-' 1206 | $testsFailed = 0 1207 | 1208 | #region Get Interfaces 1209 | If ($PSBoundParameters.ContainsKey('SwitchName')) { 1210 | $VMSwitchTeam = Get-VMSwitchTeam -Name $SwitchName -ErrorAction SilentlyContinue 1211 | 1212 | if ($VMSwitchTeam) { $Interfaces = Get-Interfaces -SwitchName $SwitchName } 1213 | Else { Write-Host "`'$SwitchName`' is not a Switch Embedded Team" -ForegroundColor Red ; break } 1214 | } 1215 | Elseif ($PSBoundParameters.ContainsKey('InterfaceNames')) { 1216 | $NetAdapters = Get-NetAdapter -Name $InterfaceNames -ErrorAction SilentlyContinue 1217 | # Not sure I understand this PowerShell funkyness but if I have only 1 adapter, the 'Count' Method is not available 1218 | # Therefore, we need to check if there's only 1 interface name and make sure there's an entry in NetAdapters 1219 | # Or check that there are more than 1 adapter 1220 | 1221 | if ($NetAdapters.Count) { $AdapterCount = $NetAdapters.Count } 1222 | elseif ($NetAdapters) { $AdapterCount = 1 } 1223 | else { $AdapterCount = 0 } 1224 | 1225 | If (-not($InterfaceNames.Count -eq $AdapterCount)) { 1226 | if ($NetAdapters) { 1227 | foreach ($Adapter in ($InterfaceNames -notmatch $NetAdapters.Name)) { 1228 | Write-Host "The interface `'$Adapter`' was not found" -ForegroundColor Red 1229 | } 1230 | } 1231 | Else { Write-Host "No interfaces found with the specified names" -ForegroundColor Red } 1232 | 1233 | break 1234 | } 1235 | Else { $Interfaces = Get-Interfaces -InterfaceNames $InterfaceNames } 1236 | } 1237 | ElseIf ($PSBoundParameters.ContainsKey('InterfaceIndex')) { 1238 | $Interfaces = Get-Interfaces -InterfaceIndex $InterfaceIndex 1239 | } 1240 | #endregion Get Interfaces 1241 | 1242 | #region Node State 1243 | 1244 | #region LLDP RSAT Tools Install 1245 | $computerInfo = Get-ComputerInfo -Property WindowsInstallationType, csmodel -ErrorAction SilentlyContinue 1246 | 1247 | if ($computerInfo.WindowsInstallationType -eq 'Client') { 1248 | $isLLDPInstalled = Get-WindowsCapability -Online -ErrorAction SilentlyContinue | Where-Object Name -like *LLDP* 1249 | 1250 | if ($isLLDPInstalled.State -eq 'Installed') { 1251 | Write-Host "[$Pass] Is Installed: RSAT Data Center Bridging LLDP Tools" 1252 | $toolsInstalled = $true 1253 | } 1254 | else { 1255 | Write-Host "[$Fail] Is Installed: RSAT Data Center Bridging LLDP Tools" -ForegroundColor Red 1256 | $toolsInstalled = $false 1257 | $testsFailed ++ 1258 | } 1259 | } 1260 | else { 1261 | $isLLDPInstalled = Get-WindowsFeature 'RSAT-DataCenterBridging-LLDP-Tools' -ErrorAction SilentlyContinue 1262 | 1263 | if ($isLLDPInstalled.InstallState -eq 'Installed') { 1264 | Write-Host "[$Pass] Is Installed: RSAT-DataCenterBridging-LLDP-Tools" 1265 | $toolsInstalled = $true 1266 | } 1267 | else { 1268 | Write-Host "[$Fail] Is Installed: RSAT-DataCenterBridging-LLDP-Tools" -ForegroundColor Red 1269 | $toolsInstalled = $false 1270 | $testsFailed ++ 1271 | } 1272 | } 1273 | 1274 | Remove-Variable PassFail, isLLDPInstalled -ErrorAction SilentlyContinue 1275 | 1276 | if ($computerInfo.CsModel -ne 'Virtual Machine') { 1277 | Write-Host "[$Pass] Is Physical Host: True" 1278 | $NodeModel = $computerInfo.CsModel 1279 | } 1280 | else { 1281 | Write-Host "[$Fail] Is Physical Host: False" -ForegroundColor Red 1282 | $NodeModel = $computerInfo.CsModel 1283 | $testsFailed ++ 1284 | } 1285 | #endregion 1286 | 1287 | #region Event log exists and is enabled 1288 | $isEvtLogEnabled = Get-WinEvent -ListLog 'Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic' -ErrorAction SilentlyContinue 1289 | 1290 | if ($isEvtLogEnabled) { 1291 | Write-Host "[$Pass] Is Found: Event Log (Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic)" 1292 | $EvtLogFound = $true 1293 | } 1294 | Else { 1295 | Write-Host "[$Fail] Is Found: Event Log (Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic)" -ForegroundColor Red 1296 | $EvtLogFound = $false 1297 | $testsFailed ++ 1298 | } 1299 | 1300 | if ($isEvtLogEnabled.IsEnabled) { 1301 | Write-Host "[$Pass] Is Enabled: Event Log (Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic)" 1302 | $EvtLogEnabled = $true 1303 | } 1304 | Else { 1305 | Write-Host "[$Fail] Is Enabled: Event Log (Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic)" -ForegroundColor Red 1306 | $EvtLogEnabled = $true 1307 | $testsFailed ++ 1308 | } 1309 | 1310 | if ($isEvtLogEnabled.FileSize -lt ($isEvtLogEnabled.MaximumSizeInBytes * .9)) { 1311 | Write-Host "[$Pass] Is NOT Full: Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic" 1312 | $isNotFull = $true 1313 | } 1314 | Else { 1315 | Write-Host "[$Fail] Is NOT Full: Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic" -ForegroundColor Red 1316 | $isNotFull = $false 1317 | $testsFailed ++ 1318 | } 1319 | #endregion 1320 | 1321 | if ($NodeStateOnly) { 1322 | $NodeState = @{} 1323 | 1324 | $NodeState = [PSCustomObject] @{ 1325 | Node = $Env:COMPUTERNAME 1326 | Model = $NodeModel 1327 | LLDPInstalled = $toolsInstalled 1328 | EvtLogFound = $EvtLogFound 1329 | EvtLogEnabled = $EvtLogEnabled 1330 | EvtLogNotFull = $isNotFull 1331 | } 1332 | 1333 | Return $NodeState 1334 | } 1335 | #endregion Node State 1336 | 1337 | #region Adapter State 1338 | $remainingIndexes = $Interfaces.ifIndex 1339 | 1340 | <# 1341 | IndexesMissingEvents is returned from Get-LLDPEvents. We reset before calling the function; any interface index found in this variable 1342 | after the function call did not have an LLDP 10041 packet in the event log. 1343 | #> 1344 | $global:IndexesMissingEvents = $Null 1345 | 1346 | # doesn't appear to be used... 1347 | $lldpEvent = Get-LLDPEvents -RemainingIndexes $remainingIndexes 1348 | 1349 | if ($AdapterStateOnly) { $AdapterState = @() } 1350 | foreach ($interface in $Interfaces) { 1351 | if ($interface.Status -eq 'Up') { Write-Host "[$Pass] Is Up: $($interface.Name)" } 1352 | Else { Write-Host "[$Fail] Is Up: $($interface.Name)" -ForegroundColor Red; $testsFailed ++ } 1353 | 1354 | if ($Interface.MediaType -eq '802.3') { Write-Host "[$Pass] Is MediaType 802.3: $($interface.Name)" } 1355 | Else { Write-Host "[$Fail] Is MediaType 802.3: $($interface.Name)" -ForegroundColor Red; $testsFailed ++ } 1356 | 1357 | if ($interface.ifIndex -notin $global:IndexesMissingEvents) { 1358 | Write-Host "[$Pass] Is Found: LLDP Packet for index $($interface.Name) [Index $($interface.ifIndex)]" 1359 | $EventExistsforIndex = $true 1360 | 1361 | Remove-Variable PassFail -ErrorAction SilentlyContinue 1362 | } 1363 | Else { 1364 | Write-Host "[$Fail] Is Found: LLDP Packet for index $($interface.Name) [Index $($interface.ifIndex)]" -ForegroundColor Red 1365 | $EventExistsforIndex = $false 1366 | 1367 | $testsFailed ++ 1368 | } 1369 | 1370 | if ($AdapterStateOnly) { 1371 | $thisAdapterState = @{} 1372 | 1373 | $thisAdapterState = [PSCustomObject] @{ 1374 | Name = $interface.Name 1375 | Status = $interface.Status 1376 | MediaType = $interface.MediaType 1377 | LLDPEventExists = $EventExistsforIndex 1378 | } 1379 | 1380 | $AdapterState += $thisAdapterState 1381 | } 1382 | } 1383 | 1384 | if ($AdapterStateOnly) { return $AdapterState } 1385 | 1386 | #endregion Adapter State 1387 | 1388 | if ($testsFailed -eq 0) { Write-Host 'Successfully passed all tests' -ForegroundColor Green } 1389 | else { Write-Host "`rFailed $testsFailed tests. Please review the output before continuing." -ForegroundColor Red } 1390 | } 1391 | 1392 | function Enable-FabricInfo { 1393 | param ( 1394 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceNames', Position=0)] 1395 | [String[]] $InterfaceNames, 1396 | 1397 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceIndex')] 1398 | [UInt32[]] $InterfaceIndex, 1399 | 1400 | [Parameter(Mandatory=$true, ParameterSetName = 'SwitchName')] 1401 | [String] $SwitchName 1402 | ) 1403 | 1404 | # run LBFO testing. Used to throw a warning and nothing else. 1405 | if ($PSBoundParameters.ContainsKey('SwitchName')) 1406 | { 1407 | $null = Test-LbfoEnabled -SwitchName $SwitchName 1408 | } 1409 | elseif ($PSBoundParameters.ContainsKey('InterfaceIndex')) 1410 | { 1411 | $null = Test-LbfoEnabled -InterfaceIndex $InterfaceIndex 1412 | } 1413 | elseif ($PSBoundParameters.ContainsKey('InterfaceNames')) 1414 | { 1415 | $null = Test-LbfoEnabled -InterfaceNames $InterfaceNames 1416 | } 1417 | 1418 | $computerInfo = Get-ComputerInfo -Property WindowsInstallationType, csmodel -ErrorAction SilentlyContinue 1419 | 1420 | if ($computerInfo.CsModel -eq 'Virtual Machine') { return (Write-Error 'Cannot be enabled on a virtual machine.' -EA Stop) } 1421 | 1422 | if ($computerInfo.WindowsInstallationType -eq 'Client') { 1423 | $isLLDPInstalled = Get-WindowsCapability -Online -ErrorAction SilentlyContinue | Where-Object Name -like *LLDP* 1424 | 1425 | if ($isLLDPInstalled.State -ne 'Installed') { Add-WindowsCapability -Name Rsat.LLDP.Tools~~~~0.0.1.0 -Online } 1426 | } 1427 | else { 1428 | $isLLDPInstalled = Get-WindowsFeature -Name RSAT-DataCenterBridging-LLDP-Tools -ErrorAction SilentlyContinue 1429 | 1430 | if ($isLLDPInstalled.InstallState -ne 'Installed') { Install-WindowsFeature -Name RSAT-DataCenterBridging-LLDP-Tools } 1431 | } 1432 | 1433 | # Enable NetLLDPAgent and get logs 1434 | $LLDPLog = Get-WinEvent -ListLog Microsoft-Windows-LinkLayerDiscoveryProtocol/Diagnostic 1435 | 1436 | if ($LLDPLog.FileSize -gt ($LLDPLog.MaximumSizeInBytes * .9)) { 1437 | $LLDPLog.IsEnabled = $false 1438 | $LLDPLog.SaveChanges() 1439 | } 1440 | 1441 | if ($LLDPLog.IsEnabled -eq $false) { 1442 | $LLDPLog.IsEnabled = $true 1443 | $LLDPLog.SaveChanges() 1444 | } 1445 | 1446 | If ($PSBoundParameters.ContainsKey('SwitchName')) { 1447 | $VMSwitch = Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue 1448 | $VMSwitchTeam = Get-VMSwitchTeam -Name $SwitchName -ErrorAction SilentlyContinue 1449 | 1450 | if ($VMSwitch -and $VMSwitchTeam) { $Interfaces = Get-Interfaces -SwitchName $SwitchName } 1451 | Else { Write-Host "`'$SwitchName`' is not a Switch Embedded Team" -ForegroundColor Red ; break } 1452 | } 1453 | Elseif ($PSBoundParameters.ContainsKey('InterfaceNames')) { 1454 | $NetAdapters = Get-NetAdapter -Name $InterfaceNames -ErrorAction SilentlyContinue 1455 | # Not sure I understand this PowerShell funkyness but if I have only 1 adapter, the 'Count' Method is not available 1456 | # Therefore, we need to check if there's only 1 interface name and make sure there's an entry in NetAdapters 1457 | # Or check that there are more than 1 adapter 1458 | 1459 | if ($NetAdapters.Count) { $AdapterCount = $NetAdapters.Count } 1460 | elseif ($NetAdapters) { $AdapterCount = 1 } 1461 | else { $AdapterCount = 0 } 1462 | 1463 | If (-not($InterfaceNames.Count -eq $AdapterCount)) { 1464 | if ($NetAdapters) { 1465 | foreach ($Adapter in ($InterfaceNames -notmatch $NetAdapters.Name)) { 1466 | Write-Host "The interface `'$Adapter`' was not found" -ForegroundColor Red 1467 | } 1468 | } 1469 | Else { Write-Host "No interfaces found with the specified names" -ForegroundColor Red } 1470 | 1471 | break 1472 | } 1473 | Else { $Interfaces = Get-Interfaces -InterfaceNames $InterfaceNames } 1474 | } 1475 | ElseIf ($PSBoundParameters.ContainsKey('InterfaceIndex')) { 1476 | $Interfaces = Get-Interfaces -InterfaceIndex $InterfaceIndex 1477 | } 1478 | 1479 | $remainingIndexes = $Interfaces.ifIndex 1480 | 1481 | Enable-NetLldpAgent -InterfaceIndex $remainingIndexes 1482 | 1483 | Write-Verbose 'LLDP has been enabled for the specified interfaces; LLDP packets are typically sent every 30 seconds' 1484 | Write-Host 'Please run Test-FabricInfo to determine if all requirements have been met' 1485 | } 1486 | 1487 | function Get-FabricInfo 1488 | { 1489 | [CmdletBinding()] 1490 | param ( 1491 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceNames', Position=0)] 1492 | [String[]] 1493 | $InterfaceNames, 1494 | 1495 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceIndex')] 1496 | [String[]] 1497 | $InterfaceIndex, 1498 | 1499 | [Parameter(Mandatory=$true, ParameterSetName = 'SwitchName')] 1500 | [String] 1501 | $SwitchName 1502 | ) 1503 | 1504 | 1505 | if ($PSBoundParameters.ContainsKey('SwitchName')) 1506 | { 1507 | $lbfoEnabled = Test-LbfoEnabled -SwitchName $SwitchName 1508 | 1509 | $VMSwitch = Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue 1510 | $VMSwitchTeam = Get-VMSwitchTeam -Name $SwitchName -ErrorAction SilentlyContinue 1511 | 1512 | if ($VMSwitch -and $VMSwitchTeam) 1513 | { 1514 | $Interfaces = Get-Interfaces -SwitchName $SwitchName 1515 | } 1516 | else 1517 | { 1518 | # jakehr: using {return (Write-Error -EA Stop)} will send a terminating error back to the calling program and ends function execution. Good for automation. 1519 | return (Write-Error "'$SwitchName' is not a Switch Embedded Team" -EA Stop) 1520 | } 1521 | } 1522 | elseif ($PSBoundParameters.ContainsKey('InterfaceNames')) 1523 | { 1524 | $lbfoEnabled = Test-LbfoEnabled -InterfaceNames $InterfaceNames 1525 | 1526 | [array]$NetAdapters = Get-NetAdapter -Name $InterfaceNames -ErrorAction SilentlyContinue 1527 | # Not sure I understand this PowerShell funkyness but if I have only 1 adapter, the 'Count' Method is not available 1528 | # Therefore, we need to check if there's only 1 interface name and make sure there's an entry in NetAdapters 1529 | # Or check that there are more than 1 adapter 1530 | # 1531 | # jakehr: typecast the variable as [array] to fix the funkiness. 1532 | 1533 | $AdapterCount = $NetAdapters.Count 1534 | 1535 | If (-not($InterfaceNames.Count -eq $AdapterCount)) 1536 | { 1537 | if ($NetAdapters) 1538 | { 1539 | foreach ($Adapter in ($InterfaceNames -notmatch $NetAdapters.Name)) 1540 | { 1541 | $enumError = "The interface `'$Adapter`' was not found" 1542 | } 1543 | } 1544 | else 1545 | { 1546 | $enumError = "No interfaces found with the specified names" 1547 | } 1548 | 1549 | #break <<<<< jakehr: implies we always leave Get-FabricInfo in this code path, so converting this to a terminating error return 1550 | return (Write-Error "Interface enumeration failure. $enumError" -EA Stop) 1551 | } 1552 | else 1553 | { 1554 | [array]$Interfaces = Get-Interfaces -InterfaceNames $InterfaceNames 1555 | } 1556 | } 1557 | elseIf ($PSBoundParameters.ContainsKey('InterfaceIndex')) 1558 | { 1559 | $lbfoEnabled = Test-LbfoEnabled -InterfaceIndex $InterfaceIndex 1560 | 1561 | [array]$Interfaces = Get-Interfaces -InterfaceIndex $InterfaceIndex 1562 | 1563 | if (-NOT $Interfaces) 1564 | { 1565 | return (Write-Error "Interface enumeration failure. The interface index(es) were not found: $($InterfaceIndex -join ", ")" -EA Stop) 1566 | } 1567 | } 1568 | 1569 | # Going to try and find a SET switch. This is not a terminating error at this point. 1570 | if (-NOT $SwitchName) 1571 | { 1572 | 1573 | $VMSwitchTeam = Get-VMSwitchTeam -EA SilentlyContinue 1574 | 1575 | if ($VMSwitchTeam) 1576 | { 1577 | $SwitchName = $VMSwitchTeam | Where-Object { $_.NetAdapterInterfaceDescription[0] -in ($Interfaces.InterfaceDescription) } | ForEach-Object Name 1578 | } 1579 | } 1580 | 1581 | [int[]]$remainingIndexes = $Interfaces.ifIndex 1582 | 1583 | $lldpEvent = Get-LLDPEvents -RemainingIndexes $remainingIndexes 1584 | 1585 | if ($lldpEvent.count -ne $remainingIndexes.Count) 1586 | { 1587 | # jakehr: convert to warning text from scary red text 1588 | Write-Warning "Could not find an LLDP Packet one or more of the interfaces specified. Please run Test-FabricInfo." 1589 | } 1590 | else 1591 | { 1592 | $InterfaceTable = Parse-LLDPPacket -Events $lldpEvent 1593 | } 1594 | 1595 | #Convert To/From JSON to make a simple object with property names 1596 | $JsonTable = $InterfaceTable | ConvertTo-Json 1597 | $InterfaceTable = $JsonTable | ConvertFrom-Json 1598 | 1599 | Remove-Variable jsonTable -ErrorAction SilentlyContinue 1600 | 1601 | $ChassisGroups = $InterfaceTable | Group-Object ChassisID 1602 | #$portOrder = $ChassisGroups.Group | Sort sourceMac | Select InterfaceName, InterfaceIndex, ChassisID, SourceMac 1603 | 1604 | $InterfaceDetails = @() 1605 | #$HostNetAdapters = @() 1606 | $interfaceMap = @() 1607 | 1608 | # force array since there may only be a single vNIC team map, or a single NIC "team" 1609 | [array]$HostVNICTeamMap = Get-VMNetworkAdapterTeamMapping -ManagementOS | Where-Object NetAdapterName -in $Interfaces.Name 1610 | 1611 | foreach ($thisInterface in $Interfaces) 1612 | { 1613 | #$thisInterface = $_ 1614 | $InterfaceBinding = Get-NetAdapterBinding -Name $thisInterface.Name -ComponentID ms_tcpip, vms_pp 1615 | 1616 | if (($InterfaceBinding | Where-Object ComponentID -eq 'vms_pp').Enabled -eq $true ) 1617 | { 1618 | if ($HostVNICTeamMap) 1619 | { 1620 | try 1621 | { 1622 | $thisHostVNICParentAdapter = ($HostVNICTeamMap | Where-Object NetAdapterName -eq $thisInterface.Name).ParentAdapter 1623 | [array]$HostNetAdapterWithIP = Get-NetAdapter -Name $thisHostVNICParentAdapter.Name 1624 | } 1625 | catch 1626 | { 1627 | Write-Verbose "This interface ($($thisInterface.Name)) does not have a team mapping. Defaulting to all host vNICs." 1628 | } 1629 | } 1630 | } 1631 | else 1632 | { 1633 | [array]$HostNetAdapterWithIP = $thisInterface 1634 | } 1635 | 1636 | # This handles situations where there are no mappings between pNIC and host vNIC, which means no host vNICs were discovered 1637 | # In this scenario we can assume $thisInterface is a pNIC without a map or there is no SET switch. 1638 | if (-NOT $HostNetAdapterWithIP -and $PSBoundParameters.ContainsKey('SwitchName')) 1639 | { 1640 | $thisHostVNICParentAdapter = $thisInterface 1641 | 1642 | # since any host net adapter can use this interface we make HostNetAdapterWithIP equal to all host vNICs on the SET switch 1643 | # match on mac address since VMNetworkAdapter and NetAdapter names may not be equal, and exclude the active pNIC 1644 | [array]$HostvNicMacs = Get-VMNetworkAdapter -ManagementOS -SwitchName $SwitchName -EA SilentlyContinue | ForEach-Object { $_.MacAddress -replace '..(?!$)', '$&-' } 1645 | $HostNetAdapterWithIP = Get-NetAdapter | Where-Object { $_.MacAddress -in $HostvNicMacs -and $_.InterfaceDescription -match "Hyper-V" } 1646 | } 1647 | 1648 | foreach ($thisHostNetAdapter in $HostNetAdapterWithIP) 1649 | { 1650 | #$thisHostNetAdapter = $_ 1651 | $thisIP = Get-NetIPAddress -InterfaceIndex $thisHostNetAdapter.ifIndex -AddressFamily IPv4 -EA SilentlyContinue 1652 | 1653 | if ($thisIP) { 1654 | $thisHostNetAdapterInterfaceDetails = [InterfaceDetails]::new() 1655 | 1656 | $thisHostNetAdapterInterfaceDetails.IpAddress = $thisIP.IPAddress 1657 | $thisHostNetAdapterInterfaceDetails.PrefixLength = $thisIP.PrefixLength 1658 | $thisHostNetAdapterInterfaceDetails.SubnetMask = Convert-CIDRToMask -PrefixLength $thisIP.PrefixLength 1659 | 1660 | $SubNetInInt = Convert-IPv4ToInt -IPv4Address $thisHostNetAdapterInterfaceDetails.SubnetMask 1661 | $IPInInt = Convert-IPv4ToInt -IPv4Address $thisHostNetAdapterInterfaceDetails.IPAddress 1662 | $thisHostNetAdapterInterfaceDetails.Network = Convert-IntToIPv4 -Integer ($SubNetInInt -band $IPInInt) 1663 | $thisHostNetAdapterInterfaceDetails.Subnet = "$($thisHostNetAdapterInterfaceDetails.Network)/$($thisHostNetAdapterInterfaceDetails.PrefixLength)" 1664 | 1665 | # Device is virtual 1666 | if ($thisHostNetAdapter.ConnectorPresent -eq $false) 1667 | { 1668 | if ($thisHostVNICParentAdapter.IsolationSetting.IsolationMode -eq 'VLAN') 1669 | { 1670 | $thisHostNetAdapterInterfaceDetails.VLAN = $thisHostVNICParentAdapter.IsolationSetting.DefaultIsolationID 1671 | } 1672 | 1673 | Switch ($thisHostVNICParentAdapter.VlanSetting.OperationMode) 1674 | { 1675 | 'Access' { $thisHostNetAdapterInterfaceDetails.VLAN = $thisHostVNICParentAdapter.VlanSetting.AccessVLANID } 1676 | 'Trunk' { $thisHostNetAdapterInterfaceDetails.VLAN = $thisHostVNICParentAdapter.VlanSetting.NativeVlanId } 1677 | } 1678 | 1679 | $thisHostNetAdapterInterfaceDetails.VMNetworkAdapterName = $thisHostVNICParentAdapter.Name 1680 | $thisHostNetAdapterInterfaceDetails.NetAdapterHostVNICName = $HostNetAdapterWithIP.Name 1681 | } 1682 | else 1683 | { 1684 | $thisHostNetAdapterInterfaceDetails.VLAN = (Get-NetAdapterAdvancedProperty -Name $thisHostNetAdapter.Name -RegistryKeyword VLANID -ErrorAction SilentlyContinue).RegistryValue 1685 | } 1686 | 1687 | $interfaceDetails = [ordered] @{ 1688 | IPAddress = $thisHostNetAdapterInterfaceDetails.IPAddress 1689 | SubnetMask = $thisHostNetAdapterInterfaceDetails.SubnetMask 1690 | PrefixLength = $thisHostNetAdapterInterfaceDetails.PrefixLength 1691 | Network = $thisHostNetAdapterInterfaceDetails.Network 1692 | 1693 | Subnet = $thisHostNetAdapterInterfaceDetails.Subnet 1694 | VLAN = $thisHostNetAdapterInterfaceDetails.VLAN 1695 | 1696 | InterfaceName = $thisInterface.Name 1697 | InterfaceIndex = $thisInterface.IfIndex 1698 | 1699 | NetAdapterHostVNICName = $thisHostNetAdapterInterfaceDetails.NetAdapterHostVNICName 1700 | VMNetworkAdapterName = $thisHostNetAdapterInterfaceDetails.VMNetworkAdapterName 1701 | } 1702 | 1703 | #Convert To/From JSON to make a simple object with property names 1704 | $JsonTable = $interfaceDetails | ConvertTo-Json 1705 | $interfaceDetails = $JsonTable | ConvertFrom-Json 1706 | 1707 | $interfaceMap += $interfaceDetails 1708 | } 1709 | } 1710 | 1711 | Remove-Variable HostNetAdapterWithIP -EA SilentlyContinue 1712 | } 1713 | 1714 | $Mapping = @{} 1715 | $interfaces | ForEach-Object { 1716 | $thisInterfaceName = $_.Name 1717 | $Mapping.$thisInterfaceName += @{ 1718 | Fabric = $InterfaceTable | Where-Object InterfaceName -eq $thisInterfaceName 1719 | InterfaceDetails = $interfaceMap | Where-Object InterfaceName -eq $thisInterfaceName 1720 | } 1721 | } 1722 | 1723 | $Mapping += @{ ChassisGroups = $ChassisGroups } 1724 | 1725 | return $Mapping 1726 | } 1727 | 1728 | function Start-FabricCapture { 1729 | <# 1730 | .SYNOPSIS 1731 | Performs a packet capture of LLDP packets for the specified interfaces 1732 | 1733 | .EXAMPLE 1734 | Start-FabricCapture 1735 | 1736 | .NOTES 1737 | Author: Windows Core Networking team @ Microsoft 1738 | 1739 | Please file issues on GitHub @ GitHub.com/Microsoft/DataCenterBridging 1740 | 1741 | .LINK 1742 | Windows Networking Blog : https://aka.ms/MSFTNetworkBlog 1743 | #> 1744 | 1745 | [CmdletBinding(DefaultParameterSetName = 'InterfaceNames')] 1746 | param ( 1747 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceNames', Position=0)] 1748 | [String[]] $InterfaceNames, 1749 | 1750 | [Parameter(Mandatory=$true, ParameterSetName = 'InterfaceIndex')] 1751 | [String[]] $InterfaceIndex, 1752 | 1753 | [Parameter(Mandatory=$true, ParameterSetName = 'SwitchName')] 1754 | [String] $SwitchName, 1755 | 1756 | #Typical LLDP interval is 30 seconds, so adding 1 to ensure we capture something 1757 | [Parameter(Mandatory=$false)] 1758 | [int] $CaptureTime = 31 1759 | ) 1760 | 1761 | #region InterfaceNames 1762 | If ($PSBoundParameters.ContainsKey('SwitchName')) { 1763 | $VMSwitchTeam = Get-VMSwitchTeam -Name $SwitchName -ErrorAction SilentlyContinue 1764 | 1765 | if ($VMSwitchTeam) { $Interfaces = Get-Interfaces -SwitchName $SwitchName } 1766 | Else { Write-Host "`'$SwitchName`' is not a Switch Embedded Team" -ForegroundColor Red ; break } 1767 | } 1768 | Elseif ($PSBoundParameters.ContainsKey('InterfaceNames')) { 1769 | $NetAdapters = Get-NetAdapter -Name $InterfaceNames -ErrorAction SilentlyContinue 1770 | # Not sure I understand this PowerShell funkyness but if I have only 1 adapter, the 'Count' Method is not available 1771 | # Therefore, we need to check if there's only 1 interface name and make sure there's an entry in NetAdapters 1772 | # Or check that there are more than 1 adapter 1773 | 1774 | if ($NetAdapters.Count) { $AdapterCount = $NetAdapters.Count } 1775 | elseif ($NetAdapters) { $AdapterCount = 1 } 1776 | else { $AdapterCount = 0 } 1777 | 1778 | If (-not($InterfaceNames.Count -eq $AdapterCount)) { 1779 | if ($NetAdapters) { 1780 | foreach ($Adapter in ($InterfaceNames -notmatch $NetAdapters.Name)) { 1781 | Write-Host "The interface `'$Adapter`' was not found" -ForegroundColor Red 1782 | } 1783 | } 1784 | Else { Write-Host "No interfaces found with the specified names" -ForegroundColor Red } 1785 | 1786 | break 1787 | } 1788 | Else { $Interfaces = Get-Interfaces -InterfaceNames $InterfaceNames } 1789 | } 1790 | ElseIf ($PSBoundParameters.ContainsKey('InterfaceIndex')) { 1791 | $Interfaces = Get-Interfaces -InterfaceIndex $InterfaceIndex 1792 | } 1793 | #endregion InterfaceNames 1794 | 1795 | <# Won't use the powershell cmdlets because we want to capture from Interface pNIC01 only 1796 | # LLDP uses multicast to send the port data and therefore src/dst address doesn't match to the pNIC#> 1797 | 1798 | # Ensuring no previous messes exist 1799 | $interfaceNames | ForEach-Object { 1800 | netsh.exe trace stop session=$_ | Out-Null 1801 | Get-NetEventSession | Stop-NetEventSession -ErrorAction SilentlyContinue 1802 | Get-NetEventSession | Remove-NetEventSession -ErrorAction SilentlyContinue 1803 | } 1804 | 1805 | New-Item -Path "$PSScriptRoot\Capture" -ItemType Directory -Force | Out-Null 1806 | Write-Host "Beginning capture..." 1807 | 1808 | $StartTime = Get-Date -format:'ddHHmmss' 1809 | $interfaceNames | ForEach-Object { 1810 | # netsh trace show CaptureFilterHelp has a ton of filter information 1811 | netsh.exe trace start CaptureInterface=$_ Ethernet.Type=0x88cc capture=yes session=$_ correlation=disabled report=disabled PacketTruncateBytes=65000 tracefile="$PSScriptRoot\capture\$StartTime$_.etl" | Out-Null 1812 | } 1813 | 1814 | Write-Host "Sleeping during capture for $CaptureTime seconds" 1815 | Start-Sleep -Seconds $CaptureTime 1816 | 1817 | Write-Host 'Capture complete' 1818 | Write-Host 'Converting capture to WireShark format' 1819 | 1820 | $interfaceNames | ForEach-Object { 1821 | # netsh trace show CaptureFilterHelp has a ton of filter information 1822 | netsh.exe trace stop session=$_ | Out-Null 1823 | 1824 | & "$PSScriptRoot\capture\etl2pcapng.exe" "$PSScriptRoot\capture\$StartTime$_.etl" "$PSScriptRoot\capture\$StartTime$_.pcapng" | Out-Null 1825 | 1826 | $PCAPNG = Get-Item "$PSScriptRoot\capture\$StartTime$_.pcapng" -ErrorAction SilentlyContinue 1827 | 1828 | if ($PCAPNG) { 1829 | Write-Host "`rETL Capture is available at: $("$PSScriptRoot\capture\$StartTime$_.etl")" 1830 | Write-Host "Wireshark Capture (.pcapng) is available at: $("$PSScriptRoot\capture\$StartTime$_.pcapng")" 1831 | } 1832 | else { 1833 | throw "`rWireshark Capture (.pcapng) was not successfully generated. 1834 | `rVerify you the latest C runtime is installed on this system (https://aka.ms/VCRedist) and try again. 1835 | `r`rOnce resolved, you can run Start-FabricCapture again, or manually convert the file using the command:`r`r 1836 | $PSScriptRoot\capture\etl2pcapng.exe $PSScriptRoot\capture\$StartTime$_.etl $PSScriptRoot\capture\$StartTime$_.pcapng" 1837 | } 1838 | } 1839 | } 1840 | #endregion Exportable 1841 | #endregion FabricInfo --------------------------------------------------------------------------------