modules/SdnDiag.Common/SdnDiag.Common.psm1
# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. Using module .\SdnDiag.Common.Helper.psm1 Import-Module $PSScriptRoot\SdnDiag.Common.Helper.psm1 Import-Module $PSScriptRoot\..\SdnDiag.Utilities\SdnDiag.Utilities.psm1 # create local variable to store configuration data $configurationData = Import-PowerShellDataFile -Path "$PSScriptRoot\SdnDiag.Common.Config.psd1" New-Variable -Name 'SdnDiagnostics_Common' -Scope 'Script' -Force -Value @{ Cache = @{} Config = $configurationData } ##### FUNCTIONS AUTO-POPULATED BELOW THIS LINE DURING BUILD ##### function Add-SdnDiagTraceMapping { param ( [Parameter(Mandatory=$true)] [string]$MacAddress, [Parameter(Mandatory=$true)] [string]$InfraHost, [Parameter(Mandatory=$false)] [string]$PortId, [Parameter(Mandatory=$false)] [string]$PortName, [Parameter(Mandatory=$false)] [string]$NicName, [Parameter(Mandatory=$false)] [string]$VmName, [Parameter(Mandatory=$false)] [string]$VmInternalId, [Parameter(Mandatory=$false)] [string[]]$PrivateIpAddress ) $cacheName = 'TraceMapping' $mapping = @{ MacAddress = $MacAddress PortId = $PortId PortName = $PortName NicName = $NicName VmName = $VmName VmInternalId = $VmInternalId InfraHost = $InfraHost PrivateIpAddress = $PrivateIpAddress } if (!$Script:SdnDiagnostics_Common.Cache.ContainsKey($cacheName)) { $Script:SdnDiagnostics_Common.Cache.Add($cacheName, @{}) } if (!$Script:SdnDiagnostics_Common.Cache[$cacheName].ContainsKey($InfraHost.ToLower())) { $Script:SdnDiagnostics_Common.Cache[$cacheName].Add($InfraHost.ToLower(), @{}) } $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost.ToLower()][$MacAddress] += $mapping } function Copy-CertificateToFabric { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'NetworkControllerRest')] [Parameter(Mandatory = $true, ParameterSetName = 'NetworkControllerNode')] [Parameter(Mandatory = $true, ParameterSetName = 'LoadBalancerMuxNode')] [Parameter(Mandatory = $true, ParameterSetName = 'ServerNode')] [System.String]$CertFile, [Parameter(Mandatory = $false, ParameterSetName = 'NetworkControllerRest')] [Parameter(Mandatory = $false, ParameterSetName = 'NetworkControllerNode')] [Parameter(Mandatory = $false, ParameterSetName = 'LoadBalancerMuxNode')] [Parameter(Mandatory = $false, ParameterSetName = 'ServerNode')] [System.Security.SecureString]$CertPassword, [Parameter(Mandatory = $true, ParameterSetName = 'NetworkControllerRest')] [Parameter(Mandatory = $true, ParameterSetName = 'NetworkControllerNode')] [Parameter(Mandatory = $true, ParameterSetName = 'LoadBalancerMuxNode')] [Parameter(Mandatory = $true, ParameterSetName = 'ServerNode')] [System.Object]$FabricDetails, [Parameter(Mandatory = $true, ParameterSetName = 'NetworkControllerRest')] [Switch]$NetworkControllerRestCertificate, [Parameter(Mandatory = $false, ParameterSetName = 'NetworkControllerRest')] [System.Boolean]$InstallToSouthboundDevices = $false, [Parameter(Mandatory = $true, ParameterSetName = 'NetworkControllerNode')] [Switch]$NetworkControllerNodeCert, [Parameter(Mandatory = $true, ParameterSetName = 'LoadBalancerMuxNode')] [Switch]$LoadBalancerMuxNodeCert, [Parameter(Mandatory = $true, ParameterSetName = 'ServerNode')] [Switch]$ServerNodeCert, [Parameter(Mandatory = $false, ParameterSetName = 'NetworkControllerRest')] [Parameter(Mandatory = $false, ParameterSetName = 'NetworkControllerNode')] [Parameter(Mandatory = $false, ParameterSetName = 'LoadBalancerMuxNode')] [Parameter(Mandatory = $false, ParameterSetName = 'ServerNode')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) # if we are installing the rest certificate and need to seed certificate to southbound devices # then define the variables to know which nodes must be updated if ($PSCmdlet.ParameterSetName -ieq 'NetworkControllerRest' -and $InstallToSouthboundDevices) { $southBoundNodes = @() if ($null -ne $FabricDetails.LoadBalancerMux) { $southBoundNodes += $FabricDetails.LoadBalancerMux } if ($null -ne $FabricDetails.Server) { $southBoundNodes += $FabricDetails.Server } } $certFileInfo = Get-Item -Path $CertFile -ErrorAction Stop switch ($certFileInfo.Extension) { '.pfx' { if ($CertPassword) { $certData = (Get-PfxData -FilePath $certFileInfo.FullName -Password $CertPassword).EndEntityCertificates } else { $certData = Get-PfxCertificate -FilePath $certFileInfo.FullName } } '.cer' { $certData = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certData.Import($certFileInfo) } default { throw New-Object System.NotSupportedException("Unsupported certificate extension") } } switch ($PSCmdlet.ParameterSetName) { 'LoadBalancerMuxNode' { foreach ($controller in $FabricDetails.NetworkController) { # if the certificate being passed is self-signed, we will need to copy the certificate to the other controller nodes # within the fabric and install under localmachine\root as appropriate if ($certData.Subject -ieq $certData.Issuer) { "Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` $certData.Subject, $certData.Thumbprint, $controller | Trace-Output [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1) if (-NOT (Test-Path -Path $param1 -PathType Container)) { New-Item -Path $param1 -ItemType Directory -Force } } -ArgumentList $certFileInfo.Directory.FullName Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\Root') -ErrorAction Stop } else { "No action required for {0}" -f $certData.Thumbprint | Trace-Output -Level:Verbose } } } 'NetworkControllerRest' { # copy the pfx certificate for the rest certificate to all network controllers within the cluster # and import to localmachine\my cert directory foreach ($controller in $FabricDetails.NetworkController) { "Processing {0}" -f $controller | Trace-Output -Level:Verbose "[REST CERT] Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` $certData.Subject, $certData.Thumbprint, $controller | Trace-Output if (Test-ComputerNameIsLocal -ComputerName $controller) { $importCert = Import-SdnCertificate -FilePath $certFileInfo.FullName -CertPassword $CertPassword -CertStore 'Cert:\LocalMachine\My' # if the certificate was detected as self signed # we will then copy the .cer file returned from the previous command to all the southbound nodes to install if ($importCert.SelfSigned -and $InstallToSouthboundDevices) { Install-SdnDiagnostics -ComputerName $southBoundNodes -Credential $Credential -ErrorAction Stop "[REST CERT] Installing self-signed certificate to southbound devices" | Trace-Output Invoke-PSRemoteCommand -ComputerName $southBoundNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1) if (-NOT (Test-Path -Path $param1 -PathType Container)) { $null = New-Item -Path $param1 -ItemType Directory -Force } } -ArgumentList $importCert.CerFileInfo.Directory.FullName foreach ($sbNode in $southBoundNodes) { "[REST CERT] Installing self-signed certificate to {0}" -f $sbNode | Trace-Output Copy-FileToRemoteComputer -ComputerName $sbNode -Credential $Credential -Path $importCert.CerFileInfo.FullName -Destination $importCert.CerFileInfo.FullName $null = Invoke-PSRemoteCommand -ComputerName $sbNode -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1,[Parameter(Position = 1)][String]$param2) Import-SdnCertificate -FilePath $param1 -CertStore $param2 } -ArgumentList @($importCert.CerFileInfo.FullName, 'Cert:\LocalMachine\Root') -ErrorAction Stop } } } else { [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1) if (-NOT (Test-Path -Path $param1 -PathType Container)) { New-Item -Path $param1 -ItemType Directory -Force } } -ArgumentList $certFileInfo.Directory.FullName Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\My') } } } 'NetworkControllerNode' { foreach ($controller in $FabricDetails.NetworkController) { "Processing {0}" -f $controller | Trace-Output -Level:Verbose # if the certificate being passed is self-signed, we will need to copy the certificate to the other controller nodes # within the fabric and install under localmachine\root as appropriate if ($certData.Subject -ieq $certData.Issuer) { "Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` $certData.Subject, $certData.Thumbprint, $controller | Trace-Output [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1) if (-NOT (Test-Path -Path $param1 -PathType Container)) { New-Item -Path $param1 -ItemType Directory -Force } } -ArgumentList $certFileInfo.Directory.FullName Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\Root') -ErrorAction Stop } else { "No action required for {0}" -f $certData.Thumbprint | Trace-Output -Level:Verbose } } } # for ServerNodes, we must distribute the server certificate and install to the cert:\localmachine\root directory on each of the # network controller nodes 'ServerNode' { foreach ($controller in $FabricDetails.NetworkController) { # if the certificate being passed is self-signed, we will need to copy the certificate to the other controller nodes # within the fabric and install under localmachine\root as appropriate if ($certData.Subject -ieq $certData.Issuer) { "Importing certificate [Subject: {0} Thumbprint:{1}] to {2}" -f ` $certData.Subject, $certData.Thumbprint, $controller | Trace-Output [System.String]$remoteFilePath = Join-Path -Path $certFileInfo.Directory.FullName -ChildPath $certFileInfo.Name $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1) if (-NOT (Test-Path -Path $param1 -PathType Container)) { New-Item -Path $param1 -ItemType Directory -Force } } -ArgumentList $certFileInfo.Directory.FullName Copy-FileToRemoteComputer -ComputerName $controller -Credential $Credential -Path $certFileInfo.FullName -Destination $remoteFilePath $null = Invoke-PSRemoteCommand -ComputerName $controller -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$param1, [Parameter(Position = 1)][SecureString]$param2, [Parameter(Position = 2)][String]$param3) Import-SdnCertificate -FilePath $param1 -CertPassword $param2 -CertStore $param3 } -ArgumentList @($remoteFilePath, $CertPassword, 'Cert:\LocalMachine\Root') -ErrorAction Stop } else { "No action required for {0}" -f $certData.Thumbprint | Trace-Output -Level:Verbose } } } } } function Copy-UserProvidedCertificateToFabric { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.IO.DirectoryInfo]$CertPath, [Parameter(Mandatory = $true)] [System.Security.SecureString]$CertPassword, [Parameter(Mandatory = $true)] [System.Object]$FabricDetails, [Parameter(Mandatory = $false)] [System.Boolean]$RotateNodeCerts = $false, [Parameter(Mandatory = $false)] [System.Boolean]$NetworkControllerHealthy = $false, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) $certificateCache = @() $certificateConfig = @{ RestCert = $null NetworkController = @{} } "Scanning certificates within {0}" -f $CertPath.FullName | Trace-Output $pfxFiles = Get-ChildItem -Path $CertPath.FullName -Filter '*.pfx' if ($null -eq $pfxFiles) { throw New-Object System.NullReferenceException("Unable to locate .pfx files under the specified CertPath location") } foreach ($pfxFile in $pfxFiles) { "Retrieving PfxData for {0}" -f $pfxFile.FullName | Trace-Output $pfxData = Get-PfxData -FilePath $pfxFile.FullName -Password $CertPassword -ErrorAction Stop $object = [PSCustomObject]@{ FileInfo = $pfxFile PfxData = $pfxData SelfSigned = $false } $certificateCache += $object } "Retrieving Rest Certificate" | Trace-Output -Level:Verbose $currentRestCertificate = Get-SdnNetworkControllerRestCertificate # enumerate the current certificates within the cache to isolate the rest certificate foreach ($cert in $certificateCache) { if ($cert.pfxdata.EndEntityCertificates.Subject -ieq $currentRestCertificate.Subject) { "Matched {0} [Subject: {1}; Thumbprint: {2}] to NC Rest Certificate" -f ` $cert.pfxFile.FileInfo.FullName, $cert.pfxData.EndEntityCertificates.Subject, $cert.pfxData.EndEntityCertificates.Thumbprint | Trace-Output -Level:Verbose $cert | Add-Member -MemberType NoteProperty -Name 'CertificateType' -Value 'NetworkControllerRest' $restCertificate = $cert $certificateConfig.RestCert = $restCertificate.pfxData.EndEntityCertificates.Thumbprint } if ($cert.pfxdata.EndEntityCertificates.Subject -ieq $cert.pfxdata.EndEntityCertificates.Issuer) { $cert.SelfSigned = $true } } # enumerate the certificates for network controller nodes if ($RotateNodeCerts) { foreach ($node in $FabricDetails.NetworkController) { "Retrieving current node certificate for {0}" -f $node | Trace-Output $currentNodeCert = Invoke-PSRemoteCommand -ComputerName $node -Credential $Credential -ScriptBlock { Get-SdnNetworkControllerNodeCertificate } -ErrorAction Stop foreach ($cert in $certificateCache) { $updatedNodeCert = $null if ($cert.PfxData.EndEntityCertificates.Subject -ieq $currentNodeCert.Subject) { $updatedNodeCert = $cert "Matched {0} [Subject: {1}; Thumbprint: {2}] to {3}" -f ` $updatedNodeCert.FileInfo.Name, $cert.PfxData.EndEntityCertificates.Subject, $cert.PfxData.EndEntityCertificates.Thumbprint, $node | Trace-Output $cert | Add-Member -MemberType NoteProperty -Name 'CertificateType' -Value 'NetworkControllerNode' break } } $certificateConfig.NetworkController[$node] = @{ Cert = $updatedNodeCert } } } # install the rest certificate to the network controllers to this node first # then seed out to the rest of the fabric $null = Import-SdnCertificate -FilePath $restCertificate.FileInfo.FullName -CertPassword $CertPassword -CertStore 'Cert:\LocalMachine\My' Copy-CertificateToFabric -CertFile $restCertificate.FileInfo.FullName -CertPassword $CertPassword -FabricDetails $FabricDetails ` -NetworkControllerRestCertificate -InstallToSouthboundDevices:$NetworkControllerHealthy -Credential $Credential # install the nc node certificate to other network controller nodes if self-signed if ($RotateNodeCerts) { foreach ($controller in $FabricDetails.NetworkController) { "Processing {0} for node certificates" -f $controller | Trace-Output -Level:Verbose $nodeCertConfig = $certificateConfig.NetworkController[$controller] # if we have identified a network controller node certificate then proceed # with installing the cert locally (if matches current node) if ($null -ne $nodeCertConfig.Cert.FileInfo.FullName) { if (Test-ComputerNameIsLocal -ComputerName $controller) { $null = Import-SdnCertificate -FilePath $nodeCertConfig.Cert.FileInfo.FullName -CertPassword $CertPassword -CertStore 'Cert:\LocalMachine\My' } # pass the certificate to sub-function to be seeded across the fabric if necassary Copy-CertificateToFabric -CertFile $nodeCertConfig.Cert.FileInfo.FullName -CertPassword $CertPassword -FabricDetails $FabricDetails -NetworkControllerNodeCert -Credential $Credential } else { "Unable to locate self-signed certificate file for {0}. Node certificate may need to be manually installed to other Network Controllers manually." -f $controller | Trace-Output -Level:Exception } } } return $certificateCache } function Export-RegistryKeyConfigDetails { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String[]]$Path, [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory ) try { # create the OutputDirectory if does not already exist if(!(Test-Path -Path $OutputDirectory.FullName -PathType Container)){ $null = New-Item -Path $OutputDirectory.FullName -ItemType Directory -Force } foreach($regKeyPath in $Path){ "Enumerating the registry key paths for {0}" -f $regkeyPath | Trace-Output -Level:Verbose $regKeyDirectories = @() $regKeyDirectories += Get-ChildItem -Path $regKeyPath -ErrorAction SilentlyContinue $regKeyDirectories += Get-ChildItem -Path $regKeyPath -Recurse -ErrorAction SilentlyContinue $regKeyDirectories = $regKeyDirectories | Sort-Object -Unique [System.String]$filePath = "{0}\Registry_{1}.txt" -f $OutputDirectory.FullName, $($regKeyPath.Replace(':','').Replace('\','_')) foreach($obj in $RegKeyDirectories){ "Scanning {0}" -f $obj.PsPath | Trace-Output -Level:Verbose try { $properties = Get-ItemProperty -Path $obj.PSPath -ErrorAction Stop } catch { "Unable to return results from {0}`n`t{1}" -f $obj.PSPath, $_.Exception | Trace-Output -Level:Warning continue } $properties | Out-File -FilePath $filePath -Encoding utf8 -Append # if the registry key item is referencing a dll, then lets get the dll properties so we can see the version and file information if($properties.Path -like "*.dll" -or $properties.Path -like "*.exe"){ "Getting file properties for {0}" -f $properties.Path | Trace-Output -Level:Verbose [System.String]$fileName = "FileInfo_{0}" -f $($properties.Path.Replace(':','').Replace('\','_').Replace('.','_')) Get-Item -Path $properties.Path | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name $fileName -FileType txt -Format List } } } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Get-GeneralConfigurationState { <# .SYNOPSIS Retrieves a common set of configuration details that is collected on any role, regardless of the role. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory ) $ProgressPreference = 'SilentlyContinue' try { [System.IO.FileInfo]$OutputDirectory = Join-Path -Path $OutputDirectory.FullName -ChildPath "General" "Collect general configuration state details" | Trace-Output if (-NOT (Initialize-DataCollection -FilePath $OutputDirectory.FullName -MinimumMB 100)) { "Unable to initialize environment for data collection" | Trace-Output -Level:Exception return } # Gather general configuration details from all nodes "Gathering network and system properties" | Trace-Output -Level:Verbose Get-NetTCPConnection | Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess, @{n="ProcessName";e={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} ` | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-NetTCPConnection' -FileType csv Get-Service | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-Service' -FileType txt -Format List Get-Process | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-Process' -FileType txt -Format List Get-Volume | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-Volume' -FileType txt -Format Table Get-ComputerInfo | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-ComputerInfo' -FileType txt Get-NetIPInterface | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-NetIPInterface' -FileType txt -Format Table Get-NetNeighbor -IncludeAllCompartments | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-NetNeighbor' -FileType txt -Format Table Get-NetRoute -AddressFamily IPv4 -IncludeAllCompartments | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-NetRoute' -FileType txt -Format Table ipconfig /allcompartments /all | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'ipconfig_allcompartments' -FileType txt "Gathering network adapter properties" | Trace-Output -Level:Verbose Get-NetAdapter | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-NetAdapter' -FileType txt -Format Table $outputDir = New-Item -Path (Join-Path -Path $OutputDirectory.FullName -ChildPath 'NetAdapter') -ItemType Directory -Force foreach($adapter in Get-NetAdapter){ Get-NetAdapter -Name $adapter.Name | Export-ObjectToFile -FilePath $outputDir.FullName -Prefix $adapter.Name -Name 'Get-NetAdapter' -FileType txt -Format List Get-NetAdapterAdvancedProperty -Name $adapter.Name ` | Export-ObjectToFile -FilePath $outputDir.FullName -Prefix $adapter.Name -Name 'Get-NetAdapterAdvancedProperty' -FileType txt -Format List } # Gather DNS client settings "Gathering DNS client properties" | Trace-Output -Level:Verbose $outputDir = New-Item -Path (Join-Path -Path $OutputDirectory.FullName -ChildPath 'DnsClient') -ItemType Directory -Force $dnsCommands = Get-Command -Verb Get -Module DnsClient foreach($cmd in $dnsCommands.Name){ Invoke-Expression -Command $cmd -ErrorAction SilentlyContinue | Export-ObjectToFile -FilePath $outputDir.FullName -Name $cmd.ToString() -FileType txt -Format List } # gather the certificates configured on the system $certificatePaths = @('Cert:\LocalMachine\My','Cert:\LocalMachine\Root') foreach ($path in $certificatePaths) { $fileName = $path.Replace(':','').Replace('\','_') Get-SdnCertificate -Path $path | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name "Get-SdnCertificate_$($fileName)" -FileType csv } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } $ProgressPreference = 'Continue' } function Get-SdnRole { <# .SYNOPSIS Retrieve the SDN Role for a given computername #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline)] [System.String]$ComputerName, [Parameter(Mandatory = $true)] [System.Object]$EnvironmentInfo ) # we know Windows has some strict requirements around NetBIOS/DNS name of the computer # so we can safely make some assumptions that if period (.) exists, then assume the ComputerName being passed into function # is a FQDN in which case we want to split the string and assign the NetBIOS name if ($ComputerName.Contains('.')) { [System.String]$computerNameNetBIOS = $ComputerName.Split('.')[0] [System.String]$computerNameFQDN = $ComputerName } # likewise, if no period (.) specified as part of the ComputerName we can assume we were passed a NetBIOS name of the object # in which case we will try to resolve via DNS. If any failures when resolving the HostName from DNS, will catch and default to # current user dns domain in best effort else { [System.String]$computerNameNetBIOS = $ComputerName try { [System.String]$computerNameFQDN = [System.Net.Dns]::GetHostByName($ComputerName).HostName } catch { [System.String]$computerNameFQDN = "$($ComputerName).$($env:USERDNSDOMAIN)" } } # enumerate the objects for each of the available SDN roles to find a match # once match is found, return the role name as string back to calling function foreach ($role in $EnvironmentInfo.Keys) { if ($role -ieq 'FabricNodes') { continue } foreach ($object in $EnvironmentInfo[$role]) { if ($object -ieq $computerNameNetBIOS -or $object -ieq $computerNameFQDN) { return $role.ToString() } } } # if we made it to here, we were unable to locate the appropriate role the computername is associated with "Unable to determine SDN role for {0}" -f $ComputerName | Trace-Output -Level:Warning return $null } function Get-TraceProviders { <# .SYNOPSIS Get ETW Trace Providers based on Role .PARAMETER Role The SDN Roles .PARAMETER Providers Allowed values are Default,Optional And All to control what are the providers needed #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [SdnRoles]$Role, [Parameter(Mandatory = $false)] [ValidateSet("Default", "Optional", "All")] [string]$Providers = "Default", [Parameter(Mandatory = $false)] [Switch]$AsString ) $traceProvidersArray = @() try { $config = Get-SdnModuleConfiguration -Role $Role.ToString() foreach ($key in $config.properties.EtwTraceProviders.Keys) { $traceProvider = $config.properties.EtwTraceProviders[$key] switch ($Providers) { "Default" { if ($traceProvider.isOptional -ne $true) { $traceProvidersArray += [PSCustomObject]@{ Name = $key Properties = $traceProvider } } } "Optional" { if ($traceProvider.isOptional -eq $true) { $traceProvidersArray += [PSCustomObject]@{ Name = $key Properties = $traceProvider } } } "All" { $traceProvidersArray += [PSCustomObject]@{ Name = $key Properties = $traceProvider } } } } # we want to be able to return string value back so it can then be passed to netsh trace command # enumerate the properties that have values to build a formatted string that netsh expects if ($PSBoundParameters.ContainsKey('AsString') -and $traceProvidersArray) { [string]$formattedString = $null foreach ($traceProvider in $traceProvidersArray) { foreach ($provider in $traceProvider.Properties.Providers) { $formattedString += "$(Format-NetshTraceProviderAsString -Provider $provider -Level $traceProvider.level -Keywords $traceProvider.keywords) " } } return $formattedString.Trim() } return $traceProvidersArray } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Reset-SdnDiagTraceMapping { $Script:SdnDiagnostics_Common.Cache['TraceMapping'] = @{} } function Start-EtwTraceSession { <# .SYNOPSIS Start the ETW trace with TraceProviders included. .PARAMETER TraceName The trace name to identify the ETW trace session .PARAMETER TraceProviders The trace providers in string format that you want to trace on .PARAMETER TraceFile The trace file that will be written. .PARAMETER MaxTraceSize Optional. Specifies the maximum size in MB for saved trace files. If unspecified, the default is 1024. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$TraceName, [Parameter(Mandatory = $true)] [string[]]$TraceProviders, [Parameter(Mandatory = $true)] [ValidateScript( { if ($_ -notmatch "(\.etl)") { throw "The file specified in the TraceFile argument must be etl extension" } return $true })] [System.IO.FileInfo]$TraceFile, [Parameter(Mandatory = $false)] [int]$MaxTraceSize = 1024 ) try { $logmanCmd = "logman create trace $TraceName -ow -o $TraceFile -nb 16 16 -bs 1024 -mode Circular -f bincirc -max $MaxTraceSize -ets" $result = Invoke-Expression -Command $logmanCmd # Session create failure error need to be reported to user to be aware, this means we have one trace session missing. # Provider add failure might be ignored and exposed via verbose trace/log file only to debug. if ("$result".Contains("Error")) { "Create session {0} failed with error {1}" -f $TraceName, "$result" | Trace-Output -Level:Warning } else { "Created session {0} with result {1}" -f $TraceName, "$result" | Trace-Output -Level:Verbose } foreach ($provider in $TraceProviders) { $logmanCmd = 'logman update trace $TraceName -p "$provider" 0xffffffffffffffff 0xff -ets' $result = Invoke-Expression -Command $logmanCmd "Added provider {0} with result {1}" -f $provider, "$result" | Trace-Output -Level:Verbose } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Start-NetshTrace { <# .SYNOPSIS Enables netsh tracing. Supports pre-configured trace providers or custom provider strings. .PARAMETER TraceProviderString The trace providers in string format that you want to trace on. .PARAMETER OutputDirectory Specifies a specific path and folder in which to save the files. .PARAMETER MaxTraceSize Optional. Specifies the maximum size in MB for saved trace files. If unspecified, the default is 1024. .PARAMETER Capture Optional. Specifies whether packet capture is enabled in addition to trace events. If unspecified, the default is No. .PARAMETER Overwrite Optional. Specifies whether this instance of the trace conversion command overwrites files that were rendered from previous trace conversions. If unspecified, the default is Yes. .PARAMETER Report Optional. Specifies whether a complementing report will be generated in addition to the trace file report. If unspecified, the default is disabled. .EXAMPLE PS> Start-NetshTrace -OutputDirectory "C:\Temp\CSS_SDN" -Capture Yes .EXAMPLE PS> Start-NetshTrace -OutputDirectory "C:\Temp\CSS_SDN" -TraceProviderString 'provider="{EB171376-3B90-4169-BD76-2FB821C4F6FB}" level=0xff' -Capture No .EXAMPLE PS> Start-NetshTrace -OutputDirectory "C:\Temp\CSS_SDN" -TraceProviderString 'provider="{EB171376-3B90-4169-BD76-2FB821C4F6FB}" level=0xff' -Capture Yes .EXAMPLE PS> Start-NetshTrace -OutputDirectory "C:\Temp\CSS_SDN" -Capture Yes -MaxTraceSize 2048 -Report Disabled .EXAMPLE #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory, [Parameter(Mandatory = $false)] [System.String]$TraceProviderString, [Parameter(Mandatory = $false)] [int]$MaxTraceSize = 1024, [Parameter(Mandatory = $false)] [ValidateSet('Yes', 'No')] [System.String]$Capture = 'No', [Parameter(Mandatory = $false)] [ValidateSet('Yes', 'No')] [System.String]$Overwrite = 'Yes', [Parameter(Mandatory = $false)] [ValidateSet('Enabled', 'Disabled')] [System.String]$Report = 'Disabled' ) try { # ensure that we at least are attempting to configure NDIS tracing or ETW provider tracing, else the netsh # command will return a generic exception that is not useful to the operator if ($Capture -ieq 'No' -and !$TraceProviderString) { throw New-Object System.Exception("You must at least specify Capture or TraceProviderString parameter") } # ensure that the directory exists and specify the trace file name if (!(Test-Path -Path $OutputDirectory.FullName -PathType Container)) { $null = New-Item -Path $OutputDirectory.FullName -ItemType Directory -Force } $traceFile = "{0}\{1}_{2}_netshTrace.etl" -f $OutputDirectory.FullName, $env:COMPUTERNAME, (Get-FormattedDateTimeUTC) # enable the network trace if ($TraceProviderString) { $cmd = "netsh trace start capture={0} {1} tracefile={2} maxsize={3} overwrite={4} report={5}" ` -f $Capture, $TraceProviderString, $traceFile, $MaxTraceSize, $Overwrite, $Report } else { $cmd = "netsh trace start capture={0} tracefile={1} maxsize={2} overwrite={3} report={4}" ` -f $Capture, $traceFile, $MaxTraceSize, $Overwrite, $Report } "Starting netsh trace" | Trace-Output "Netsh trace cmd:`n`t{0}" -f $cmd | Trace-Output -Level:Verbose $expression = Invoke-Expression -Command $cmd if ($expression -ilike "*Running*") { $object = New-Object -TypeName PSCustomObject -Property ( [Ordered]@{ Status = 'Running' FileName = $traceFile } ) } elseif ($expression -ilike "*A tracing session is already in progress*") { "A tracing session is already in progress" | Trace-Output -Level:Warning $object = New-Object -TypeName PSCustomObject -Property ( [Ordered]@{ Status = 'Running' } ) } else { # typically, the first line returned in scenarios where there was an error thrown will contain the error details $msg = $expression[0] throw New-Object System.Exception($msg) } return $object } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Stop-EtwTraceSession { <# .SYNOPSIS Stop ETW Trace Session .PARAMETER TraceName The trace name to identify the ETW trace session #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string[]]$ComputerName, [Parameter(Mandatory = $false)] [string]$TraceName = $null ) try { $logmanCmd = "logman stop $TraceName -ets" $result = Invoke-Expression -Command $logmanCmd if ("$result".Contains("Error")) { "Stop session {0} failed with error {1}" -f $TraceName, "$result" | Trace-Output -Level:Warning } else { "Stop session {0} with result {1}" -f $TraceName, "$result" | Trace-Output -Level:Verbose } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Stop-NetshTrace { <# .SYNOPSIS Disables netsh tracing. #> try { "Stopping trace" | Trace-Output $expression = Invoke-Expression -Command "netsh trace stop" if ($expression -ilike "*Tracing session was successfully stopped.*") { "Tracing was successfully stopped" | Trace-Output -Level:Verbose $object = New-Object -TypeName PSCustomObject -Property ( [Ordered]@{ Status = 'Stopped' } ) } elseif ($expression -ilike "*There is no trace session currently in progress.*") { "There is no trace session currently in progress" | Trace-Output -Level:Warning $object = New-Object -TypeName PSCustomObject -Property ( [Ordered]@{ Status = 'Not Running' } ) } else { # typically, the first line returned in scenarios where there was an error thrown will contain the error details $msg = $expression[0] throw New-Object System.Exception($msg) } return $object } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Update-SdnDiagTraceMapping { param ( [Parameter(Mandatory=$true)] [string]$MacAddress, [Parameter(Mandatory=$true)] [string]$InfraHost, [Parameter(Mandatory=$false)] [string]$PortId, [Parameter(Mandatory=$false)] [string]$PortName, [Parameter(Mandatory=$false)] [string]$NicName, [Parameter(Mandatory=$false)] [string]$VmName, [Parameter(Mandatory=$false)] [string]$VmInternalId, [Parameter(Mandatory=$false)] [string[]]$PrivateIpAddress ) $cacheName = 'TraceMapping' if($Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]){ if($PortId){ $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]['PortId'] = $PortId } if($PortName){ $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]['PortName'] = $PortName } if($NicName){ $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]['NicName'] = $NicName } if($VmName){ $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]['VmName'] = $VmName } if($VmInternalId){ $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]['VmInternalId'] = $VmInternalId } if($PrivateIpAddress){ $Script:SdnDiagnostics_Common.Cache[$cacheName][$InfraHost][$MacAddress]['PrivateIpAddress'] = $PrivateIpAddress } } } function Convert-SdnEtwTraceToTxt { <# .SYNOPSIS Used to convert existing etw (.etl) provider traces into text readable format .PARAMETER FileName ETL trace file path and name to convert .PARAMETER Destination Output file name and directory. If omitted, will use the FileName path and base name. .PARAMETER Overwrite Overwrites existing files. If omitted, defaults to no. .PARAMETER Report Generates an HTML report. If omitted, defaults to no. .EXAMPLE PS> Convert-SdnEtwTraceToTxt -FileName "C:\Temp\CSS_SDN\Trace.etl" .EXAMPLE PS> Convert-SdnEtwTraceToTxt -FileName "C:\Temp\CSS_SDN\Trace.etl" -Destination "C:\Temp\CSS_SDN_NEW\trace.txt" .EXAMPLE PS> Convert-SdnEtwTraceToTxt -FileName "C:\Temp\CSS_SDN\Trace.etl" -Overwrite Yes .EXAMPLE PS> Convert-SdnEtwTraceToTxt -FileName "C:\Temp\CSS_SDN\Trace.etl" -Report Yes #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateScript( { if ($_ -notmatch "(\.etl)") { throw "The file specified in the FileName argument must be etl extension" } return $true })] [System.String]$FileName, [Parameter(Mandatory = $false)] [System.String]$Destination, [Parameter(Mandatory = $false)] [ValidateSet('No', 'Yes')] [System.String]$Overwrite = 'No', [Parameter(Mandatory = $false)] [ValidateSet('No', 'Yes')] [System.String]$Report = 'No' ) try { $fileInfo = Get-Item -Path $FileName -ErrorAction Stop if (-NOT $PSBoundParameters.ContainsKey('Destination')) { [System.String]$Destination = $fileInfo.DirectoryName } if (-NOT (Test-Path -Path $Destination -PathType Container)) { $null = New-Item -Path $Destination -ItemType Directory -Force } [System.String]$outputFile = "{0}.txt" -f (Join-Path -Path $Destination -ChildPath $fileInfo.BaseName) [System.String]$cmd = "netsh trace convert input={0} output={1} overwrite={2} report={3}" ` -f $fileInfo.FullName, $outputFile, $Overwrite, $Report "Netsh trace cmd:`n`t{0}" -f $cmd | Trace-Output -Level:Verbose $expression = Invoke-Expression -Command $cmd # output returned is string objects, so need to manually do some mapping to correlate the properties # that can be then returned as psobject to the call if ($expression[5] -ilike "*done*") { $object = New-Object -TypeName PSCustomObject -Property ( [Ordered]@{ Status = 'Success' FileName = $outputFile } ) } else { # typically, the first line returned in scenarios where there was an error thrown will contain the error details $msg = $expression[0] throw New-Object System.Exception($msg) } return $object } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Enable-SdnVipTrace { <# .SYNOPSIS Enables network tracing on the SDN fabric infrastructure related to the specified VIP address. .PARAMETER VirtualIP Specify the Virtual IP address that you want to enable SDN fabric tracing for. .PARAMETER NcUri Specifies the Uniform Resource Identifier (URI) of the network controller that all Representational State Transfer (REST) clients use to connect to that controller. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .PARAMETER NcRestCredential Specifies a user account that has permission to access the northbound NC API interface. The default is the current user. .PARAMETER MaxTraceSize Optional. Specifies the maximum size in MB for saved trace files. If unspecified, the default is 1536. #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [System.String]$VirtualIP, [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [ValidateScript({ if ($_.Scheme -ne "http" -and $_.Scheme -ne "https") { throw New-Object System.FormatException("Parameter is expected to be in http:// or https:// format.") } return $true })] [Uri]$NcUri, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $NcRestCredential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] [int]$MaxTraceSize = 1536 ) $networkTraceNodes = @() Reset-SdnDiagTraceMapping try { # lets try and locate the resources associated with the public VIP address # the SdnPublicIpPoolUsageSummary is useful for this scenario, as it has the logic to scan both publicIpAddresses and loadBalancers # to locate the VIP IP we are looking for $publicIpAddressUsage = Get-SdnPublicIPPoolUsageSummary -NcUri $NcUri -NcRestCredential $NcRestCredential $publicIpResource = $publicIpAddressUsage | Where-Object {$_.IPAddress -ieq $VirtualIP} if ($null -ieq $publicIpResource) { throw "Unable to locate resources associated to $VirtualIP" } # get the load balancer muxes, as we will need to enable tracing on them $loadBalancerMuxes = Get-SdnLoadBalancerMux -NcUri $NcUri -Credential $NcRestCredential -ManagementAddressOnly $networkTraceNodes += $loadBalancerMuxes # we want to query the servers within the SDN fabric so we can get a list of the vfp switch ports across the hyper-v hosts # as we will use this reference to locate where the resources are located within the fabric $servers = Get-SdnServer -NcUri $NcUri -Credential $NcRestCredential -ManagementAddressOnly $Script:SdnDiagnostics_Common.Cache['VfpSwitchPorts'] = Get-SdnVfpVmSwitchPort -ComputerName $servers -Credential $Credential # determine the network interfaces associated with the public IP address $associatedResource = Get-SdnResource -NcUri $NcUri -Credential $NcRestCredential -ResourceRef $publicIpResource.AssociatedResource switch -Wildcard ($associatedResource.resourceRef) { "/loadBalancers/*" { "{0} is associated with load balancer {1}" -f $VirtualIP, $associatedResource.resourceRef | Trace-Output $ipConfigurations = $associatedResource.properties.backendAddressPools.properties.backendIPConfigurations.resourceRef } "/networkInterfaces/*" { "{0} is associated with network interface {1}" -f $VirtualIP, $associatedResource.resourceRef | Trace-Output $ipConfigurations = $associatedResource.resourceRef } # public IP address(es) should only ever be associated to load balancer or network interface resources # except in the case for the gateway pool, which we would not expect in this scenario at this time default { throw "Unable to determine associated resource type" } } $ipConfigurations | ForEach-Object { $ipConfig = Get-SdnResource -NcUri $NcUri -Credential $NcRestCredential -ResourceRef $_ "Located associated resource {0} with DIP address {1}" -f $ipConfig.resourceRef, $ipconfig.properties.privateIPAddress | Trace-Output # we need the mac address of the network interface to locate the vfp switch port # since the ipConfiguration is a subobject of the network interface, we need to split the resourceRef to get the network interface resource # since we know the resourceRefs are defined as /networkInterfaces/{guid}/ipConfigurations/{guid}, we can split on the '/' and get the 3rd element $netInterface = Get-SdnResource -NcUri $NcUri -Credential $NcRestCredential -ResourceRef "/networkInterfaces/$($_.Split('/')[2])" $macAddress = Format-MacAddress -MacAddress $netInterface.properties.privateMacAddress -Dashes $vfpPort = $Script:SdnDiagnostics_Common.Cache['VfpSwitchPorts'] | Where-Object {$_.MacAddress -ieq $macAddress} if ($null -ieq $vfpPort) { throw "Unable to locate vfp switch port for $macAddress" } "Located vfp switch port {0} on {1}" -f $vfpPort.PortName, $vfpPort.PSComputerName | Trace-Output # once we have the information we need, we can update our internal cache mapping Add-SdnDiagTraceMapping ` -MacAddress $vfpPort.MacAddress ` -InfraHost $vfpPort.PSComputerName ` -PortId $vfpPort.PortId ` -PortName $vfpPort.Portname ` -NicName $vfpPort.NICname ` -VmName $vfpPort.VMname ` -VmInternalId $vfpPort.VMID ` -PrivateIpAddress $ipConfig.properties.privateIPAddress } # once we have identified all the nodes we will enable tracing on # add the server(s) to the list of nodes we will enable tracing on # as this will be used to disable tracing once we are done $networkTraceNodes += $Script:SdnDiagnostics_Common.Cache['TraceMapping'].Keys $networkTraceNodes = $networkTraceNodes | Select-Object -Unique # ensure that we have SdnDiagnostics installed to the nodes that we need to enable tracing for Install-SdnDiagnostics -ComputerName $networkTraceNodes -Credential $Credential "Network traces will be enabled on`r`n`t - LoadBalancerMux: {0}`r`n`t - Server: {1}`r`n" ` -f ($loadBalancerMuxes -join ', '), ($Script:SdnDiagnostics_Common.Cache['TraceMapping'].Keys -join ', ') | Trace-Output # enable tracing on the infastructure $traceInfo = @() $traceInfo += Start-SdnNetshTrace -ComputerName $loadBalancerMuxes -Role 'LoadBalancerMux' -Credential $Credential -MaxTraceSize $MaxTraceSize $traceInfo += Start-SdnNetshTrace -ComputerName $Script:SdnDiagnostics_Common.Cache['TraceMapping'].Keys -Role 'Server' -Credential $Credential -MaxTraceSize $MaxTraceSize "Tracing has been enabled on the SDN infrastructure nodes {0}" -f ($traceInfo.PSComputerName -join ', ') | Trace-Output # at this point, tracing should be enabled on the sdn fabric and we can wait for user input to disable # once we receive user input, we will disable tracing on the infrastructure node(s) $null = Get-UserInput -Message "`r`nPress any key to disable tracing..." $null = Stop-SdnNetshTrace -ComputerName $networkTraceNodes -Credential $Credential "Tracing has been disabled on the SDN infrastructure. Saving configuration details to {0}\{1}_TraceMapping.json" -f (Get-WorkingDirectory), $VirtualIP | Trace-Output $Script:SdnDiagnostics_Common.Cache['TraceMapping'] | Export-ObjectToFile -FilePath (Get-WorkingDirectory) -Prefix $VirtualIP -Name 'TraceMapping' -FileType 'json' $traceFileInfo = @() foreach ($obj in $traceInfo) { $traceFileInfo += [PSCustomObject]@{ ComputerName = $obj.PSComputerName FileName = $obj.FileName } } return $traceFileInfo } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Get-SdnCertificate { <# .SYNOPSIS Returns a list of the certificates within the given certificate store. .PARAMETER Path Defines the path within the certificate store. Path is expected to start with cert:\. .EXAMPLE PS> Get-SdnCertificate -Path "Cert:\LocalMachine\My" #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'Subject')] [Parameter(Mandatory = $true, ParameterSetName = 'Thumbprint')] [ValidateScript({ if ($_ -notlike "cert:\*") { throw New-Object System.FormatException("Invalid path") } return $true })] [System.String]$Path, [Parameter(Mandatory = $false, ParameterSetName = 'Subject')] [ValidateNotNullorEmpty()] [System.String]$Subject, [Parameter(Mandatory = $false, ParameterSetName = 'Thumbprint')] [ValidateNotNullorEmpty()] [System.String]$Thumbprint ) try { $certificateList = Get-ChildItem -Path $Path -Recurse | Where-Object {$_.PSISContainer -eq $false} -ErrorAction Stop switch ($PSCmdlet.ParameterSetName) { 'Subject' { $filteredCert = $certificateList | Where-Object {$_.Subject -ieq $Subject} } 'Thumbprint' { $filteredCert = $certificateList | Where-Object {$_.Thumbprint -ieq $Thumbprint} } default { return $certificateList } } if ($null -eq $filteredCert) { "Unable to locate certificate using {0}" -f $PSCmdlet.ParameterSetName | Trace-Output -Level:Warning return $null } if ($filteredCert.NotAfter -le (Get-Date)) { "Certificate [Thumbprint: {0} | Subject: {1}] is currently expired" -f $filteredCert.Thumbprint, $filteredCert.Subject | Trace-Output -Level:Exception } return $filteredCert } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Get-SdnDiagnosticLog { <# .SYNOPSIS Collect the default enabled logs from SdnDiagnostics folder. .PARAMETER OutputDirectory Specifies a specific path and folder in which to save the files. .PARAMETER FromDate Optional parameter that allows you to control how many hours worth of logs to retrieve from the system for the roles identified. Default is 4 hours. .PARAMETER ConvertETW Optional parameter that allows you to specify if .etl trace should be converted. By default, set to $true .EXAMPLE PS> Get-SdnDiagnosticLog -OutputDirectory "C:\Temp\CSS_SDN" .EXAMPLE PS> Get-SdnDiagnosticLog -OutputDirectory "C:\Temp\CSS_SDN" -FromDate (Get-Date).AddHours(-8) #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory, [Parameter(Mandatory = $false)] [DateTime]$FromDate = (Get-Date).AddHours(-4), [Parameter(Mandatory = $false)] [bool]$ConvertETW = $true ) try { [System.IO.FileInfo]$logDir = $Script:SdnDiagnostics_Common.Config.DefaultLogDirectory [System.IO.FileInfo]$OutputDirectory = Join-Path -Path $OutputDirectory.FullName -ChildPath "SdnDiagnosticLogs" "Collect diagnostic logs between {0} and {1} UTC" -f $FromDate.ToUniversalTime(), (Get-Date).ToUniversalTime() | Trace-Output $logFiles = Get-ChildItem -Path $logDir.FullName -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -ge $FromDate } if($null -eq $logFiles){ "No log files found under {0} between {1} and {2} UTC." -f $logDir.FullName, $FromDate.ToUniversalTime(), (Get-Date).ToUniversalTime() | Trace-Output -Level:Warning return } $minimumDiskSpace = [float](Get-FolderSize -FileName $logFiles.FullName -Total).GB * 3.5 # we want to call the initialize datacollection after we have identify the amount of disk space we will need to create a copy of the logs if (-NOT (Initialize-DataCollection -FilePath $OutputDirectory.FullName -MinimumGB $minimumDiskSpace)) { "Unable to initialize environment for data collection" | Trace-Output -Level:Exception return } # copy the log files from the default log directory to the output directory "Copying {0} files to {1}" -f $logFiles.Count, $OutputDirectory.FullName | Trace-Output -Level:Verbose Copy-Item -Path $logFiles.FullName -Destination $OutputDirectory.FullName -Force # convert the most recent etl trace file into human readable format without requirement of additional parsing tools if ($ConvertETW) { $convertFile = Get-Item -Path "$($OutputDirectory.FullName)\*" -Include '*.etl' | Sort-Object -Property LastWriteTime | Select-Object -Last 1 if ($convertFile) { $null = Convert-SdnEtwTraceToTxt -FileName $convertFile.FullName -Overwrite 'Yes' } } # once we have copied the files to the new location we want to compress them to reduce disk space # if confirmed we have a .zip file, then remove the staging folder "Compressing results to {0}" -f "$($OutputDirectory.FullName).zip" | Trace-Output -Level:Verbose Compress-Archive -Path "$($OutputDirectory.FullName)\*" -Destination $OutputDirectory.FullName -CompressionLevel Optimal -Force if (Test-Path -Path "$($OutputDirectory.FullName).zip" -PathType Leaf) { Remove-Item -Path $OutputDirectory.FullName -Force -Recurse } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Get-SdnEventLog { <# .SYNOPSIS Collect the Windows Event Logs for different SDN Roles. .PARAMETER Role The specific SDN role to collect windows event logs from. .PARAMETER OutputDirectory Specifies a specific path and folder in which to save the files. .PARAMETER FromDate Optional parameter that allows you to control how many hours worth of logs to retrieve from the system for the roles identified. Default is 1 day. .EXAMPLE PS> Get-SdnEventLog -OutputDirectory "C:\Temp\CSS_SDN" .EXAMPLE PS> Get-SdnEventLog -OutputDirectory "C:\Temp\CSS_SDN" -FromDate (Get-Date).AddHours(-12) #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [SdnRoles]$Role, [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory, [parameter(Mandatory = $false)] [DateTime]$FromDate = (Get-Date).AddDays(-1) ) try { $eventLogs = [System.Collections.ArrayList]::new() [System.IO.FileInfo]$OutputDirectory = Join-Path -Path $OutputDirectory.FullName -ChildPath "EventLogs" "Collect event logs between {0} and {1} UTC" -f $FromDate.ToUniversalTime(), (Get-Date).ToUniversalTime() | Trace-Output if (-NOT (Initialize-DataCollection -Role $Role.ToString() -FilePath $OutputDirectory.FullName -MinimumGB 1)) { "Unable to initialize environment for data collection" | Trace-Output -Level:Exception return } $eventLogProviders = $config.properties.eventLogProviders "Collect the following events: {0}" -f ($eventLogProviders -join ',') | Trace-Output # build array of win events based on which role the function is being executed # we will build these and dump the results at the end foreach ($provider in $eventLogProviders) { "Looking for event matching {0}" -f $provider | Trace-Output -Level:Verbose $eventLogsToAdd = Get-WinEvent -ListLog $provider -ErrorAction SilentlyContinue | Where-Object { $_.RecordCount } if ($eventLogsToAdd.Count -gt 1) { [void]$eventLogs.AddRange($eventLogsToAdd) } elseif ($eventLogsToAdd.Count -gt 0) { [void]$eventLogs.Add($eventLogsToAdd) } else { "No events found for {0}" -f $provider | Trace-Output -Level:Warning } } foreach ($eventLog in $eventLogs) { $fileName = ("{0}\{1}" -f $OutputDirectory.FullName, $eventLog.LogName).Replace("/", "_") "Export event log {0} to {1}" -f $eventLog.LogName, $fileName | Trace-Output -Level:Verbose $events = Get-WinEvent -LogName $eventLog.LogName -ErrorAction SilentlyContinue | Where-Object { $_.TimeCreated -gt $FromDate } if ($events) { $events | Select-Object TimeCreated, LevelDisplayName, Id, ProviderName, ProviderID, TaskDisplayName, OpCodeDisplayName, Message ` | Export-Csv -Path "$fileName.csv" -NoTypeInformation -Force } wevtutil epl $eventLog.LogName "$fileName.evtx" /ow } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Import-SdnCertificate { <# .SYNOPSIS Imports certificates (CER) and private keys from a Personal Information Exchange (PFX) file to the destination store. .PARAMETER FilePath Specifies the full path to the PFX or CER file. .PARAMETER CertStore Specifies the path of the store to which certificates will be imported. If paramater is not specified, defaults to Cert:\LocalMachine\Root. .PARAMETER CertPassword Specifies the password for the imported PFX file in the form of a secure string. .EXAMPLE PS> Import-SdnCertificate -FilePath c:\certs\cert.pfx -CertStore Cert:\LocalMachine\Root .EXAMPLE PS> Import-SdnCertificate -FilePath c:\certs\cert.pfx -CertStore Cert:\LocalMachine\Root -Password $secureString #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String]$FilePath, [Parameter(Mandatory = $true)] [System.String]$CertStore, [Parameter(Mandatory = $false)] [System.Security.SecureString]$CertPassword ) $trustedRootStore = 'Cert:\LocalMachine\Root' $fileInfo = Get-Item -Path $FilePath $certObject = @{ SelfSigned = $false CertInfo = $null CerFileInfo = $null } switch ($fileInfo.Extension) { '.pfx' { if ($CertPassword) { $certData = (Get-PfxData -FilePath $fileInfo.FullName -Password $CertPassword).EndEntityCertificates } else { $certData = Get-PfxCertificate -FilePath $fileInfo.FullName } } '.cer' { $certData = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certData.Import($fileInfo) } default { throw New-Object System.NotSupportedException("Unsupported certificate extension") } } $certExists = Get-ChildItem -Path $CertStore | Where-Object {$_.Thumbprint -ieq $certData.Thumbprint} if ($certExists) { "{0} already exists under {1}" -f $certExists.Thumbprint, $CertStore | Trace-Output -Level:Verbose $certObject.CertInfo = $certExists } else { "Importing {0} to {1}" -f $certData.Thumbprint, $CertStore | Trace-Output if ($certData.HasPrivateKey) { $importCert = Import-PfxCertificate -FilePath $fileInfo.FullName -CertStoreLocation $CertStore -Password $CertPassword -Exportable -ErrorAction Stop Set-SdnCertificateAcl -Path $CertStore -Thumbprint $importCert.Thumbprint } else { $importCert = Import-Certificate -FilePath $fileInfo.FullName -CertStoreLocation $CertStore -ErrorAction Stop } $certObject.CertInfo = $importCert } # determine if the certificates being used are self signed if ($certObject.CertInfo.Subject -ieq $certObject.CertInfo.Issuer) { "Detected the certificate subject and issuer are the same. Setting SelfSigned to true" | Trace-Output -Level:Verbose $certObject.SelfSigned = $true # check to see if we installed to root store with above operation # if it is not, then we want to check the root store to see if this certificate has already been installed # and finally if does not exist, then export the certificate from current store and import into trusted root store if ($CertStore -ine $trustedRootStore) { $selfSignedCerExists = Get-ChildItem -Path $trustedRootStore | Where-Object {$_.Thumbprint -ieq $certObject.CertInfo.Thumbprint} [System.String]$selfSignedCerPath = "{0}\{1}.cer" -f (Split-Path $fileInfo.FullName -Parent), ($certObject.CertInfo.Subject).Replace('=','_') $selfSignedCer = Export-Certificate -Cert $certObject.CertInfo -FilePath $selfSignedCerPath -ErrorAction Stop $certObject.CerFileInfo = $selfSignedCer if (-NOT ($selfSignedCerExists)) { # import the certificate to the trusted root store "Importing public key to {0}" -f $trustedRootStore | Trace-Output $null = Import-Certificate -FilePath $selfSignedCer.FullName -CertStoreLocation $trustedRootStore -ErrorAction Stop } else { "{0} already exists under {1}" -f $certObject.CertInfo.Thumbprint, $trustedRootStore | Trace-Output -Level:Verbose } } } return $certObject } function Invoke-SdnGetNetView { <# .SYNOPSIS Invokes Get-Netview function on the specified ComputerNames. .PARAMETER OutputDirectory Specifies a specific path and folder in which to save the files. .PARAMETER BackgroundThreads Maximum number of background tasks, from 0 - 16. Defaults to 5. .PARAMETER SkipAdminCheck If present, skip the check for admin privileges before execution. Note that without admin privileges, the scope and usefulness of the collected data is limited. .PARAMETER SkipLogs If present, skip the EVT and WER logs gather phases. .PARAMETER SkipNetshTrace If present, skip the Netsh Trace data gather phases. .PARAMETER SkipCounters If present, skip the Windows Performance Counters (WPM) data gather phases. .PARAMETER SkipVM If present, skip the Virtual Machine (VM) data gather phases. .EXAMPLE PS> Invoke-SdnGetNetView -OutputDirectory "C:\Temp\CSS_SDN" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.IO.FileInfo]$OutputDirectory, [Parameter(Mandatory = $false)] [int]$BackgroundThreads = 5, [Parameter(Mandatory = $false)] [switch]$SkipAdminCheck, [Parameter(Mandatory = $false)] [switch]$SkipLogs, [Parameter(Mandatory = $false)] [switch]$SkipNetshTrace, [Parameter(Mandatory = $false)] [switch]$SkipCounters, [Parameter(Mandatory = $false)] [switch]$SkipVm ) try { Copy-Item -Path "$PSScriptRoot\..\..\..\externalPackages\Get-NetView" -Destination "C:\Program Files\WindowsPowerShell\Modules\" -Force -Recurse Import-Module -Name 'Get-NetView' -Force "Using Get-NetView version {0}" -f (Get-Module -Name 'Get-NetView' -ErrorAction SilentlyContinue).Version.ToString() | Trace-Output -Level:Verbose [System.IO.FileInfo]$OutputDirectory = Join-Path -Path $OutputDirectory.FullName -ChildPath "NetView" # validate the output directory exists, else create the appropriate path if (!(Test-Path -Path $OutputDirectory.FullName -PathType Container)) { $null = New-Item -Path $OutputDirectory.FullName -ItemType Directory -Force } # execute Get-NetView with specified parameters and redirect all streams to null to prevent unnecessary noise on the screen Get-NetView -OutputDirectory $OutputDirectory.FullName ` -BackgroundThreads $BackgroundThreads ` -SkipAdminCheck:$SkipAdminCheck.IsPresent ` -SkipLogs:$SkipLogs.IsPresent ` -SkipNetshTrace:$SkipNetshTrace.IsPresent ` -SkipCounters:$SkipCounters.IsPresent ` -SkipVm:$SkipVm.IsPresent *> $null # remove the uncompressed files and folders to free up ~ 1.5GB of space $compressedArchive = Get-ChildItem -Path $OutputDirectory.FullName -Filter "*.zip" if ($compressedArchive) { Get-ChildItem -Path $OutputDirectory.FullName -Exclude *.zip | Remove-Item -Recurse -Confirm:$false } return $compressedArchive.FullName } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function New-SdnCertificate { <# .SYNOPSIS Creates a new self-signed certificate for use with SDN fabric. .PARAMETER Subject Specifies the string that appears in the subject of the new certificate. This cmdlet prefixes CN= to any value that does not contain an equal sign. .PARAMETER CertStoreLocation Specifies the certificate store in which to store the new certificate. If paramater is not specified, defaults to Cert:\LocalMachine\My. .PARAMETER NotAfter Specifies the date and time, as a DateTime object, that the certificate expires. To obtain a DateTime object, use the Get-Date cmdlet. The default value for this parameter is one year after the certificate was created. .EXAMPLE PS> New-SdnCertificate -Subject rest.sdn.contoso -CertStoreLocation Cert:\LocalMachine\My #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.String]$Subject, [Parameter(Mandatory = $false)] [ValidateScript({ if ($_ -notlike "cert:\*") { throw New-Object System.FormatException("Invalid path") } return $true })] [System.String]$CertStoreLocation = 'Cert:\LocalMachine\My', [Parameter(Mandatory = $true)] [System.DateTime]$NotAfter ) try { "Generating certificate with subject {0} under {1}" -f $Subject, $CertStoreLocation | Trace-Output $selfSignedCert = New-SelfSignedCertificate -Type Custom -KeySpec KeyExchange -Subject $Subject ` -KeyExportPolicy Exportable -HashAlgorithm sha256 -KeyLength 2048 ` -CertStoreLocation $CertStoreLocation -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2,1.3.6.1.4.1.311.95.1.1.1") ` -NotAfter $NotAfter if ($selfSignedCert) { "Successfully generated self signed certificate`n`tSubject: {0}`n`tThumbprint: {1}`n`tNotAfter: {2}" ` -f $selfSignedCert.Subject, $selfSignedCert.Thumbprint, $selfSignedCert.NotAfter | Trace-Output Set-SdnCertificateAcl -Path $CertStoreLocation -Thumbprint $selfSignedCert.Thumbprint } return $selfSignedCert } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Set-SdnCertificateAcl { <# .SYNOPSIS Configures NT AUTHORITY/NETWORK SERVICE to have appropriate permissions to the private key of the Network Controller certificates. .PARAMETER Path Specifies the certificate store in which to retrieve the certificate. .PARAMETER Subject Gets the thumbprint of a certificate with the specified store to ensure correct ACLs are defined. .PARAMETER Thumbprint Gets the thumbprint of a certificate with the specified store to ensure correct ACLs are defined. .EXAMPLE PS> Set-SdnCertificateAcl -Path CERT:\LocalMachine\My -Subject 'NCREST.Contoso.Local' #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Subject')] [Parameter(Mandatory = $true, ParameterSetName = 'Thumbprint')] [ValidateScript({ if ($_ -notlike "cert:\*") { throw New-Object System.FormatException("Invalid path") } return $true })] [System.String]$Path, [Parameter(Mandatory = $true, ParameterSetName = 'Subject')] [System.String]$Subject, [Parameter(Mandatory = $true, ParameterSetName = 'Thumbprint')] [System.String]$Thumbprint ) try { switch ($PSCmdlet.ParameterSetName) { 'Subject' { $certificate = Get-SdnCertificate -Path $Path -Subject $Subject } 'Thumbprint' { $certificate = Get-SdnCertificate -Path $Path -Thumbprint $Thumbprint } } if ($null -eq $certificate) { throw New-Object System.NullReferenceException("Unable to locate the certificate based on $($PSCmdlet.ParameterSetName)") } else { "Located certificate with Thumbprint: {0} and Subject: {1}" -f $certificate.Thumbprint, $certificate.Subject | Trace-Output -Level:Verbose } if ($certificate.Count -ge 2) { throw New-Object System.Exception("Multiple certificates found matching $($PSCmdlet.ParameterSetName)") } if ($certificate.HasPrivateKey) { $privateKeyCertFile = Get-Item -Path "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\*" | Where-Object {$_.Name -ieq $($certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)} $privateKeyAcl = Get-Acl -Path $privateKeyCertFile.FullName if ($privateKeyAcl.Access.IdentityReference -inotcontains "NT AUTHORITY\NETWORK SERVICE") { $networkServicePermission = "NT AUTHORITY\NETWORK SERVICE", "Read", "Allow" "Configuring {0} on {1}" -f ($networkServicePermission -join ', ').ToString(), $privateKeyCertFile.FullName | Trace-Output $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($networkServicePermission) [void]$privateKeyAcl.AddAccessRule($accessRule) $null = Set-Acl -Path $privateKeyCertFile.FullName -AclObject $privateKeyAcl } else { "Permissions already defined for NT AUTHORITY\NETWORK SERVICE for {0}. No ACL changes required." -f $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName | Trace-Output -Level:Verbose } } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Start-SdnDataCollection { <# .SYNOPSIS Automated data collection script to pull the current configuration state in conjuction with diagnostic logs and other data points used for debugging. .PARAMETER NetworkController Specifies the name or IP address of the network controller node on which this cmdlet operates. The parameter is optional if running on network controller node. .PARAMETER NcUri Specifies the Uniform Resource Identifier (URI) of the network controller that all Representational State Transfer (REST) clients use to connect to that controller. .PARAMETER Role The specific SDN role(s) to collect configuration state and logs from. .PARAMETER ComputerName Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more remote computers. .PARAMETER OutputDirectory Directory the results will be saved to. If ommitted, will default to the current working directory. .PARAMETER IncludeNetView If enabled, will execute Get-NetView on the Role(s) or ComputerName(s) defined. .PARAMETER IncludeLogs If enabled, will collect the diagnostic logs from the Role(s) or ComputerName(s) defined. Works in conjunction with the FromDate parameter. .PARAMETER FromDate Optional parameter that allows you to control how many hours worth of logs to retrieve from the system for the roles identified. If ommitted, defaults to 4 hours. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .PARAMETER NcRestCredential Specifies a user account that has permission to access the northbound NC API interface. The default is the current user. .PARAMETER Limit Used in conjuction with the Role parameter to limit how many nodes per role operations are performed against. If ommitted, defaults to 16. .PARAMETER ConvertETW Optional parameter that allows you to specify if .etl trace should be converted. By default, set to $true .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role Gateway,NetworkController,Server,LoadBalancerMux .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role Gateway,NetworkController,Server,LoadBalancerMux -IncludeLogs .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role Gateway,Server,LoadBalancerMux -IncludeLogs -FromDate (Get-Date).AddHours(-1) -Credential (Get-Credential) .EXAMPLE PS> Start-SdnDataCollection -NetworkController 'Contoso-NC01' -Role LoadBalancerMux -IncludeLogs -IncludeNetView #> [CmdletBinding(DefaultParameterSetName = 'Role')] param ( [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.String]$NetworkController = $(HostName), [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [ValidateScript({ if ($_.Scheme -ne "http" -and $_.Scheme -ne "https") { throw New-Object System.FormatException("Parameter is expected to be in http:// or https:// format.") } return $true })] [Uri]$NcUri, [Parameter(Mandatory = $true, ParameterSetName = 'Role')] [SdnRoles[]]$Role, [Parameter(Mandatory = $true, ParameterSetName = 'Computer')] [System.String[]]$ComputerName, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.IO.FileInfo]$OutputDirectory = (Get-WorkingDirectory), [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [Switch]$IncludeNetView, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [Switch]$IncludeLogs, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [DateTime]$FromDate = (Get-Date).AddHours(-4), [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $NcRestCredential = [System.Management.Automation.PSCredential]::Empty, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Int]$Limit = 16, [Parameter(Mandatory = $false, ParameterSetName = 'Role')] [Parameter(Mandatory = $false, ParameterSetName = 'Computer')] [bool]$ConvertETW = $true ) try { if (-NOT ($PSBoundParameters.ContainsKey('NetworkController'))) { $config = Get-SdnModuleConfiguration -Role 'NetworkController' $confirmFeatures = Confirm-RequiredFeaturesInstalled -Name $config.windowsFeature if (-NOT ($confirmFeatures)) { "The current machine is not a NetworkController, run this on NetworkController or use -NetworkController parameter to specify one" | Trace-Output -Level:Warning return # don't throw exception, since this is a controlled scenario and we do not need stack exception tracing } } [System.String]$childPath = 'SdnDataCollection_{0}' -f (Get-FormattedDateTimeUTC) [System.IO.FileInfo]$OutputDirectory = Join-Path -Path $OutputDirectory.FullName -ChildPath $childPath [System.IO.FileInfo]$workingDirectory = (Get-WorkingDirectory) [System.IO.FileInfo]$tempDirectory = "$(Get-WorkingDirectory)\Temp" $dataCollectionNodes = @() $filteredDataCollectionNodes = @() # setup the directory location where files will be saved to "Starting SDN Data Collection" | Trace-Output if ($IncludeLogs) { $minGB = 10 } else { $minGB = 5 } if (-NOT (Initialize-DataCollection -FilePath $OutputDirectory.FullName -MinimumGB $minGB)) { "Unable to initialize environment for data collection" | Trace-Output -Level:Exception return } "Results will be saved to {0}" -f $OutputDirectory.FullName | Trace-Output # generate a mapping of the environment if ($NcUri) { $sdnFabricDetails = Get-SdnInfrastructureInfo -NetworkController $NetworkController -Credential $Credential -NcUri $NcUri.AbsoluteUri -NcRestCredential $NcRestCredential } else { $sdnFabricDetails = Get-SdnInfrastructureInfo -NetworkController $NetworkController -Credential $Credential -NcRestCredential $NcRestCredential } switch ($PSCmdlet.ParameterSetName) { 'Role' { foreach ($value in $Role) { foreach ($node in $sdnFabricDetails[$value.ToString()]) { $object = [PSCustomObject]@{ Role = $value Name = $node } "Node {0} with role {1} added for data collection" -f $object.Name, $object.Role | Trace-Output $dataCollectionNodes += $object } } } 'Computer' { foreach ($computer in $ComputerName) { $computerRole = Get-SdnRole -ComputerName $computer -EnvironmentInfo $sdnFabricDetails if ($computerRole) { $object = [PSCustomObject]@{ Role = $computerRole Name = $computer } "Node {0} with role {1} added for data collection" -f $object.Name, $object.Role | Trace-Output $dataCollectionNodes += $object } } } } if ($null -eq $dataCollectionNodes) { throw New-Object System.NullReferenceException("No data nodes identified") } $dataCollectionNodes = $dataCollectionNodes | Sort-Object -Property Name -Unique $groupedObjectsByRole = $dataCollectionNodes | Group-Object -Property Role # ensure SdnDiagnostics installed across the data nodes and versions are the same Install-SdnDiagnostics -ComputerName $dataCollectionNodes.Name -ErrorAction Stop # collect control plane information without regardless of roles defined $slbStateInfo = Get-SdnSlbStateInformation -NcUri $sdnFabricDetails.NcUrl -Credential $NcRestCredential $slbStateInfo | ConvertTo-Json -Depth 100 | Out-File "$($OutputDirectory.FullName)\SlbState.Json" Invoke-SdnResourceDump -NcUri $sdnFabricDetails.NcUrl -OutputDirectory $OutputDirectory.FullName -Credential $NcRestCredential Get-SdnNetworkControllerState -NetworkController $NetworkController -OutputDirectory $OutputDirectory.FullName -Credential $Credential -NcRestCredential $NcRestCredential Get-SdnNetworkControllerClusterInfo -NetworkController $NetworkController -OutputDirectory $OutputDirectory.FullName -Credential $Credential $debugInfraHealthResults = Get-SdnFabricInfrastructureResult if ($debugInfraHealthResults) { $debugInfraHealthResults.Values | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnFabricInfrastructureResult_Summary' -FileType 'txt' -Format 'table' $debugInfraHealthResults | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnFabricInfrastructureResult' -FileType 'json' } # enumerate through each role and collect appropriate data foreach ($group in $groupedObjectsByRole | Sort-Object -Property Name) { if ($PSCmdlet.ParameterSetName -eq 'Role') { if ($group.Group.Name.Count -ge $Limit) { "Exceeded node limit for role {0}. Limiting nodes to the first {1} nodes" -f $group.Name, $Limit | Trace-Output -Level:Warning } $dataNodes = $group.Group.Name | Select-Object -First $Limit } else { $dataNodes = $group.Group.Name } "Performing cleanup of {0} directory across {1}" -f $tempDirectory.FullName, ($dataNodes -join ', ') | Trace-Output Clear-SdnWorkingDirectory -Path $tempDirectory.FullName -Recurse -ComputerName $dataNodes -Credential $Credential # add the data nodes to new variable, to ensure that we pick up the log files specifically from these nodes # to account for if filtering was applied $filteredDataCollectionNodes += $dataNodes "Collect configuration state details for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output switch ($group.Name) { 'Gateway' { Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory) Get-SdnGatewayConfigurationState -OutputDirectory $OutputDirectory } -ArgumentList $tempDirectory.FullName -AsJob -PassThru -Activity 'Get-SdnGatewayConfigurationState' } 'NetworkController' { Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory) Get-SdnNetworkControllerConfigurationState -OutputDirectory $OutputDirectory } -ArgumentList $tempDirectory.FullName -AsJob -PassThru -Activity 'Get-SdnNetworkControllerConfigurationState' } 'Server' { Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory) Get-SdnServerConfigurationState -OutputDirectory $OutputDirectory } -ArgumentList $tempDirectory.FullName -AsJob -PassThru -Activity 'Get-SdnServerConfigurationState' Get-SdnProviderAddress -ComputerName $dataNodes -Credential $Credential ` | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnProviderAddress' -FileType csv Get-SdnVfpVmSwitchPort -ComputerName $dataNodes -Credential $Credential ` | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnVfpVmSwitchPort' -FileType csv Get-SdnVMNetworkAdapter -ComputerName $dataNodes -Credential $Credential -AsJob -PassThru -Timeout 900 ` | Export-ObjectToFile -FilePath $OutputDirectory.FullName -Name 'Get-SdnVMNetworkAdapter' -FileType csv } 'LoadBalancerMux' { Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory) Get-SdnSlbMuxConfigurationState -OutputDirectory $OutputDirectory } -ArgumentList $tempDirectory.FullName -AsJob -PassThru -Activity 'Get-SdnSlbMuxConfigurationState' } } # check to see if any network traces were captured on the data nodes previously "Checking for any previous network traces and moving them into {0}" -f $tempDirectory.FullName | Trace-Output Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param ([Parameter(Position = 0)][String]$NetworkTraceDir, [Parameter(Position = 1)][String]$TempDirectory, [Parameter(Position = 2)]$ConvertETW) if (Test-Path -Path $NetworkTraceDir -PathType Container) { # convert the most recent etl trace file into human readable format without requirement of additional parsing tools if ($ConvertETW) { $convertFile = Get-Item -Path "$NetworkTraceDir\*" -Include '*.etl' | Sort-Object -Property LastWriteTime | Select-Object -Last 1 if ($convertFile) { $null = Convert-SdnEtwTraceToTxt -FileName $convertFile.FullName -Overwrite 'Yes' } } # move the entire directory try { Move-Item -Path $NetworkTraceDir -Destination $TempDirectory -Force -ErrorAction Stop } catch { "Unable to move {0} to {1}`n`t{2}" -f $NetworkTraceDir, $TempDirectory, $_.Exception | Write-Warning } } } -ArgumentList @("$($workingDirectory.FullName)\NetworkTraces", $tempDirectory.FullName, $ConvertETW) # collect the sdndiagnostics etl files if IncludeLogs was provided if ($IncludeLogs) { if ($group.Name -ieq 'NetworkController') { "Collect service fabric logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory, [Parameter(Position = 1)][DateTime]$FromDate) Get-SdnServiceFabricLog -OutputDirectory $OutputDirectory -FromDate $FromDate } -ArgumentList @($tempDirectory.FullName, $FromDate) -AsJob -PassThru -Activity 'Get-SdnServiceFabricLog' } if ($group.Name -ieq 'Server') { Get-SdnAuditLog -NcUri $sdnFabricDetails.NcUrl -NcRestCredential $NcRestCredential -OutputDirectory "$($OutputDirectory.FullName)\AuditLogs" ` -ComputerName $dataNodes -Credential $Credential } "Collect diagnostics logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory, [Parameter(Position = 1)][DateTime]$FromDate, [Parameter(Position = 2)]$ConvertETW) Get-SdnDiagnosticLog -OutputDirectory $OutputDirectory -FromDate $FromDate -ConvertETW $ConvertETW } -ArgumentList @($tempDirectory.FullName, $FromDate, $ConvertETW) -AsJob -PassThru -Activity 'Get-SdnDiagnosticLog' "Collect event logs for {0} nodes: {1}" -f $group.Name, ($dataNodes -join ', ') | Trace-Output Invoke-PSRemoteCommand -ComputerName $dataNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory, [Parameter(Position =1)][String]$Role, [Parameter(Position =2)][DateTime]$FromDate) Get-SdnEventLog -OutputDirectory $OutputDirectory -Role $Role -FromDate $FromDate } -ArgumentList @($tempDirectory.FullName, $group.Name, $FromDate) -AsJob -PassThru -Activity 'Get-SdnEventLog' } } if ($IncludeNetView) { "Collect Get-NetView logs for {0}" -f ($filteredDataCollectionNodes -join ', ') | Trace-Output $null = Invoke-PSRemoteCommand -ComputerName $filteredDataCollectionNodes -Credential $Credential -ScriptBlock { param([Parameter(Position = 0)][String]$OutputDirectory) Invoke-SdnGetNetView -OutputDirectory $OutputDirectory ` -SkipAdminCheck ` -SkipNetshTrace ` -SkipVM ` -SkipCounters } -ArgumentList @($tempDirectory.FullName) -AsJob -PassThru -Activity 'Invoke-SdnGetNetView' } foreach ($node in $filteredDataCollectionNodes) { [System.IO.FileInfo]$formattedDirectoryName = Join-Path -Path $OutputDirectory.FullName -ChildPath $node.ToLower() Copy-FileFromRemoteComputer -Path $tempDirectory.FullName -Destination $formattedDirectoryName.FullName -ComputerName $node -Credential $Credential -Recurse -Force Copy-FileFromRemoteComputer -Path (Get-TraceOutputFile) -Destination $formattedDirectoryName.FullName -ComputerName $node -Credential $Credential -Force } # check for any failed PS remoting jobs and copy them to data collection if (Test-Path -Path "$(Get-WorkingDirectory)\PSRemoteJob_Failures") { Copy-Item -Path "$(Get-WorkingDirectory)\PSRemoteJob_Failures" -Destination $formattedDirectoryName.FullName -Recurse } "Performing cleanup of {0} directory across {1}" -f $tempDirectory.FullName, ($filteredDataCollectionNodes -join ', ') | Trace-Output Clear-SdnWorkingDirectory -Path $tempDirectory.FullName -Recurse -ComputerName $filteredDataCollectionNodes -Credential $Credential Copy-Item -Path (Get-TraceOutputFile) -Destination $OutputDirectory.FullName "`Data collection completed. Logs have been saved to {0}" -f $OutputDirectory.FullName | Trace-Output -Level:Success } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Start-SdnEtwTraceCapture { <# .SYNOPSIS Start ETW Trace capture based on Role .PARAMETER Role The SDN Roles .PARAMETER Providers Allowed values are Default,Optional And All to control what are the providers needed #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [SdnRoles]$Role, [Parameter(Mandatory = $false)] [System.String]$OutputDirectory = (Get-WorkingDirectory), [Parameter(Mandatory = $false)] [ValidateSet("Default", "Optional", "All")] [string]$Providers = "Default" ) # this is the default trace size that we will limit each etw trace session to $maxTraceSize = 1024 try { $traceProvidersArray = Get-TraceProviders -Role $Role.ToString() -Providers $Providers # we want to calculate the max size on number of factors to ensure sufficient disk space is available $diskSpaceRequired = $maxTraceSize*($traceProvidersArray.Count)*1.5 if (-NOT (Initialize-DataCollection -Role $Role.ToString() -FilePath $OutputDirectory -MinimumMB $diskSpaceRequired)) { "Unable to initialize environment for data collection" | Trace-Output -Level:Exception return } foreach ($traceProviders in $traceProvidersArray) { "Starting trace session {0}" -f $traceProviders.name | Trace-Output -Level:Verbose Start-EtwTraceSession -TraceName $traceProviders.name -TraceProviders $traceProviders.properties.providers -TraceFile "$OutputDirectory\$($traceProviders.name).etl" -MaxTraceSize $maxTraceSize } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Start-SdnNetshTrace { <# .SYNOPSIS Enables netsh tracing based on pre-configured trace providers. .PARAMETER ComputerName Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more remote computers. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the password. .PARAMETER Role The specific SDN role of the local or remote computer(s) that tracing is being enabled for. .PARAMETER OutputDirectory Specifies a specific path and folder in which to save the files. .PARAMETER MaxTraceSize Optional. Specifies the maximum size in MB for saved trace files. If unspecified, the default is 1024. .PARAMETER Capture Optional. Specifies whether packet capture is enabled in addition to trace events. If unspecified, the default is No. .PARAMETER Overwrite Optional. Specifies whether this instance of the trace conversion command overwrites files that were rendered from previous trace conversions. If unspecified, the default is Yes. .PARAMETER Report Optional. Specifies whether a complementing report will be generated in addition to the trace file report. If unspecified, the default is disabled. .EXAMPLE PS> Start-SdnNetshTrace -OutputDirectory "C:\Temp\CSS_SDN" -Capture Yes -Role Server .EXAMPLE PS> Start-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController 'PREFIX-NC03').Server -Role Server -Credential (Get-Credential) #> [CmdletBinding(DefaultParameterSetName = 'Local')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Local')] [Parameter(Mandatory = $true, ParameterSetName = 'Remote')] [SdnRoles]$Role, [Parameter(Mandatory = $false, ParameterSetName = 'Local')] [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [System.String]$OutputDirectory = "$(Get-WorkingDirectory)\NetworkTraces", [Parameter(Mandatory = $false, ParameterSetName = 'Local')] [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [int]$MaxTraceSize = 1536, [Parameter(Mandatory = $false, ParameterSetName = 'Local')] [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [ValidateSet('Yes', 'No')] [System.String]$Capture = 'Yes', [Parameter(Mandatory = $false, ParameterSetName = 'Local')] [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [ValidateSet('Yes', 'No')] [System.String]$Overwrite = 'Yes', [Parameter(Mandatory = $false, ParameterSetName = 'Local')] [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [ValidateSet('Enabled', 'Disabled')] [System.String]$Report = 'Disabled', [Parameter(Mandatory = $false, ParameterSetName = 'Local')] [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [ValidateSet("Default", "Optional", "All")] [string]$Providers = "All", [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [System.String[]]$ComputerName, [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) $params = @{ OutputDirectory = $OutputDirectory MaxTraceSize = $MaxTraceSize Capture = $Capture Overwrite = $Overwrite Report = $Report } $scriptBlock = { param( [Parameter(Position = 0)][SdnRoles]$Role, [Parameter(Position = 1)][String]$OutputDirectory, [Parameter(Position = 2)][int]$MaxTraceSize, [Parameter(Position = 3)][String]$Capture, [Parameter(Position = 4)][String]$Overwrite, [Parameter(Position = 5)][String]$Report, [Parameter(Position = 6)][String]$Providers ) Start-SdnNetshTrace -Role $Role.ToString() -OutputDirectory $OutputDirectory ` -MaxTraceSize $MaxTraceSize -Capture $Capture -Overwrite $Overwrite -Report $Report -Providers $Providers } try { if ($PSCmdlet.ParameterSetName -eq 'Remote') { Invoke-PSRemoteCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock ` -ArgumentList @($Role.ToString(), $params.OutputDirectory, $params.MaxTraceSize, $params.Capture, $params.Overwrite, $params.Report, $Providers) } else { $traceProviderString = Get-TraceProviders -Role $Role.ToString() -Providers $Providers -AsString if ($traceProviderString) { $params.Add('TraceProviderString', $traceProviderString) "Trace providers configured: {0}" -f $traceProviderString | Trace-Output -Level:Verbose } elseif ($null -eq $traceProviderString) { "No default trace providers found for role {0}." | Trace-Output if ($params.Capture -eq 'No') { $params.Capture = 'Yes' "Setting capture to {1}" -f $Role, $params.Capture | Trace-Output } } if (-NOT ( Initialize-DataCollection -Role $Role.ToString() -FilePath $OutputDirectory -MinimumMB ($MaxTraceSize*1.5) )) { "Unable to initialize environment for data collection" | Trace-Output -Level:Exception return } Start-NetshTrace @params } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Stop-SdnEtwTraceCapture { <# .SYNOPSIS Start ETW Trace capture based on Role .PARAMETER Role The SDN Roles .PARAMETER Providers Allowed values are Default,Optional And All to control what are the providers needed #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [SdnRoles]$Role, [Parameter(Mandatory = $false)] [ValidateSet("Default", "Optional", "All")] [string]$Providers = "Default" ) try { $traceProvidersArray = Get-TraceProviders -Role $Role -Providers $Providers foreach ($traceProviders in $traceProvidersArray) { Stop-EtwTraceSession -TraceName $traceProviders.name } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } function Stop-SdnNetshTrace { <# .SYNOPSIS Disables netsh tracing. .PARAMETER ComputerName Type the NetBIOS name, an IP address, or a fully qualified domain name of one or more remote computers. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the password. #> [CmdletBinding(DefaultParameterSetName = 'Local')] param ( [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [System.String[]]$ComputerName, [Parameter(Mandatory = $false, ParameterSetName = 'Remote')] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) try { if ($PSCmdlet.ParameterSetName -eq 'Remote') { Invoke-PSRemoteCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock { Stop-SdnNetshTrace } } else { Stop-NetshTrace } } catch { "{0}`n{1}" -f $_.Exception, $_.ScriptStackTrace | Trace-Output -Level:Error } } |