diff --git a/Kubernetes/windows/debug/Start-SdnDebugTrace.ps1 b/Kubernetes/windows/debug/Start-SdnDebugTrace.ps1 new file mode 100644 index 00000000..b4c6ccce --- /dev/null +++ b/Kubernetes/windows/debug/Start-SdnDebugTrace.ps1 @@ -0,0 +1,438 @@ +#Requires -RunAsAdministrator + +[CmdletBinding()] +param +( + [Parameter(Position=0)] + [ValidateSet("Lite", "Normal", "Full")] + [string] + $Scenario = "Normal", + + # Path with filename where the ETL file will be saved. Format: \.etl + [Parameter(Position=1)] + [string] + $EtlFile = "C:\server.etl", + + # How many bytes of the packet to collect. Default is 256 bytes to collect encapsulated headers. + [Parameter(Position=2)] + [int] + $snapLen = 256, + + # Maximum file size in megabytes. 0 means that there is no maximum + [Parameter(Position=3)] + [int] + $maxFileSize = 500, + + # Path to where the log files will be written. + [Parameter(Position=1)] + [string] + $LogPath = $PSScriptRoot, + + # Does not prompt/pause execution and wait on user input. + [switch] + $NoPrompt, + + # Does not collect network packets. + [switch] + $NoPackets, + + # Collects logs after user presses q to stop tracing. Ignored when -NoPrompt set. + [switch] + $NoLogs +) + +begin { + ### FUNCTIONS ### + #region + # Downloads a file from the Internet. + # Returns the full path to the download. + # This function is needed to download supporting files when they are missing. + function Get-WebFile { + param ( + [string]$URI, + [string]$Path, + [string]$FileName + ) + + Write-Debug "Get-WebFile - Start." + + # validate path + if ( -NOT (Test-Path "$Path" -IsValid) ) { + return (Write-Error "The save path, $Path, is not valid. Error: $_" -EA Stop) + } + + # create the path if missing + if ( -NOT (Get-Item "$Path" -EA SilentlyContinue) ) { + try { + $null = mkdir "$Path" -Force -EA Stop + } catch { + return (Write-Error "The save path, $Path, does not exist and cannot be created. Error: $_" -EA Stop) + } + + } + + # create the full path + $OutFile = "$Path\$fileName" + + # use curl if it is found in the path + # options are iwr (Invoke-WebRequest (default)), bits (Start-BitsTransfer), and curl (preferred when found) + $dlMethods = "iwr", "curl", "bits" + $dlMethod = "iwr" + + # switch to curl when found + $curlFnd = Get-Command "curl.exe" -EA SilentlyContinue + if ($curlFnd) { $dlMethod = "curl" } + + Write-Verbose "Get-WebFile - Attempting download of $URI to $OutFile" + + # did the download work? + $dlWorked = $false + + # methods tried + # initialize with curl because if curl is found then we're using it, if it's not found then we shouldn't try it + $tried = @("curl") + + # loop through + do { + switch ($dlMethod) { + # tracks whether + "curl" { + Write-Verbose "Get-WebFile - Download with curl." + + Push-Location "$Path" + # download with curl + # -L = download location + # -o = output file + # -s = Silent + curl.exe -L $URI -o $OutFile -s + Pop-Location + } + + "iwr" { + Write-Verbose "Get-WebFile - Download with Invoke-WebRequest." + + # make sure we don't try to use an insecure SSL/TLS protocol when downloading files + Write-Debug "Get-WebFile - Disabling unsupported SSL/TLS protocls." + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12, [System.Net.SecurityProtocolType]::Tls13 + + # download silently with iwr + $oldProg = $global:ProgressPreference + $Global:ProgressPreference = "SilentlyContinue" + $null = Invoke-WebRequest -Uri $URI -OutFile "$OutFile" -MaximumRedirection 5 -PassThru + $Global:ProgressPreference = $oldProg + } + + "bits" { + Write-Verbose "Get-WebFile - Download with Start-BitsTransfer." + + # download silently with iwr + $oldProg = $global:ProgressPreference + $Global:ProgressPreference = "SilentlyContinue" + $null = Start-BitsTransfer -Source $URI -Destination "$OutFile" + $Global:ProgressPreference = $oldProg + } + + Default { return (Write-Error "An unknown download method was selected. This should not happen. dlMethod: $_" -EA Stop) } + } + + # is there a file, any file, then consider this a success + $dlFnd = Get-Item "$OutFile" -EA SilentlyContinue + + if ( -NOT $dlFnd ) { + # change download method and try again + Write-Verbose "Failed to download using $dlMethod." + + if ($tried.Count -lt $dlMethods.Count) { + if ($dlMethod -notin $tried) { + $tried += $dlMethod + Write-Verbose "Get-WebFile - Added $dlMethod to tried: $($tried -join ', ')" + } + + :dl foreach ($dl in $dlMethods) { + if ($dl -notin $tried) { + Write-Verbose "Get-WebFile - Switching to $dl method." + $dlMethod = $dl + $tried += $dl + break dl + } + } + } else { + return (Write-Error "The download has failed!" -EA Stop) + } + } else { + # exit the loop + $dlWorked = $true + } + } until ($dlWorked) + + Write-Verbose "Get-WebFile - File downloaded to $OutFile." + + #Add-Log "Downloaded successfully to: $output" + Write-Debug "Get-WebFile - Returning: $OutFile" + Write-Debug "Get-WebFile - End." + return $OutFile + } + + # checks the required file and tries to download it when missing + # this is a pre-requisite requirement before loading modules + function Test-RequiredFile { + [CmdletBinding()] + param ( + [Parameter()] + [string[]] + $RequiredFiles, + + [Parameter()] + [string[]] + $Dir, + + [Parameter()] + [string[]] + $DlRoot + ) + + #Write-Verbose "Test-RequiredFile - " + Write-Verbose "Test-RequiredFile - begin" + + # controls whether the test is a success + $valid = $true + + # local path to look for files + $lclPath = "$PSScriptRoot\$Dir" + Write-Verbose "Test-RequiredFile - lclPath: $lclPath" + + # if the dir doesn't exist then create it and download the files in the next section + $dirFnd = Get-Item "$lclPath" -EA SilentlyContinue + if (-NOT $dirFnd) { + # create the dir + try { + $null = mkdir "$lclPath" -Force -EA Stop + Write-Verbose "Test-RequiredFile - Created $lclPath" + } catch { + return ( Write-Error "Failed to create the $Dir directory. Error: $_" -EA Stop ) + } + } + + # look for the files locally, try to download if missing + foreach ($file in $RequiredFiles) { + # looking + $flObj = Get-Item "$lclPath\$file" -EA SilentlyContinue + if ( -NOT $flObj ) { + # try to download + try { + $flStr = Get-WebFile -URI "$DlRoot\$file" -Path $lclPath -FileName $file + + # double check + $flObj = Get-Item "$flStr" -EA SilentlyContinue + + if ( -NOT $flObj ) { + Write-Verbose "Test-RequiredFile - Failed to validate file: $file" + $valid = $false + } + } catch { + $valid = $false + Write-Warning "Failed to download $file. URL: $DlRoot\$file" + } + } else { + Write-Verbose "Test-RequiredFile - Found file: $file" + } + } + + + + Write-Verbose "Test-RequiredFile - end" + return $valid + } + + #endregion FUNCTIONS + + #Write-Verbose "Start-SdnDebug Tracing - " + Write-Verbose "Start-SdnDebug Tracing - Pre-requisite work." + + # the repo root + $sdnDebugRepoRoot = 'https://raw.githubusercontent.com/JamesKehr/SDN/master/Kubernetes/windows/debug' + + # look for library files + $libFiles = "libClass.psm1", "libFunction.psm1", "libLogging.psm1" + $libDir = "lib" + $libDlRoot = "$sdnDebugRepoRoot/lib" + Write-Verbose "Start-SdnDebug Tracing - Validating lib files." + $checkLib = Test-RequiredFile -RequiredFiles $libFiles -Dir $libDir -DlRoot $libDlRoot + + + # look for profile files + $profileFiles = "hns_full.json", "hns_normal.json", "hns_lite.json" + $profilesDir = "profiles" + $profilesDlRoot = "$sdnDebugRepoRoot/profiles" + Write-Verbose "Start-SdnDebug Tracing - Validating profile files." + $checkProfiles = Test-RequiredFile -RequiredFiles $profileFiles -Dir $profilesDir -DlRoot $profilesDlRoot + + if ( $checkLib -eq $false -or $checkProfiles -eq $false ) { + return ( Write-Warning @" +Failed to find or download a required file. Please download the required files and try again: +`t- URL: $libDlRoot +`t- Sub-directory: lib +`t`t- $($libFiles -join "`n`t`t- ") +`t- URL: $profilesDlRoot +`t- Sub-directory: profiles +`t`t- $($profileFiles -join "`n`t`t- ") +"@ + ) + } else { + Write-Verbose "Start-SdnDebug Tracing - All files validated. Proceeding with tracing." + } + + + ### load lib ### + foreach ($file in $libFiles) { + try { + Write-Verbose "Start-SdnDebugTracing - Importing module: $file" + Import-Module "$PSScriptRoot\$libDir\$file" -EA Stop + } catch { + return (Write-Error "Failed to import a required module: $PSScriptRoot\$libDir\$file") + } + } + + ### start libLogging ### + + Start-Logging -ModuleName "Start-SdnDebugTracing" -LogPath $LogPath + + ### start using libLogging cmdlets after this point ### + Write-Log "Begin" + + Write-Log "Loading the hns_$Scenario capture profile." + try { + $providers = Import-HnsProfile "$PSScriptRoot\$profilesDir\hns_$Scenario`.json" + } catch { + Write-LogError -Text "Failed to load providers. Error: $_" -Code "PROVIDER_LOAD_FAILURE" + } + + + ## CONSTANTS ## + # capture name + $sessionName = 'HnsPacketCapture' + + Write-Log "Found $($providers.Count) providers." + Write-Log "Begin - End" +} + +process { + Write-Log "Process - Begin" + + ## setup the trace + # + # Stop any existing session and create a new session + # + Write-Log "Cleaning up any failed $sessionName sessions." + Stop-NetEventSession $sessionName -ErrorAction Ignore | Out-Null + Remove-NetEventSession $sessionName -ErrorAction Ignore | Out-Null + + # + # create capture session + # + try { + Write-Log "Creating the $sessionName capture session." + New-NetEventSession $sessionName -CaptureMode SaveToFile -MaxFileSize $maxFileSize -LocalFilePath $EtlFile -EA Stop | Out-Null + } catch { + return (Write-Error "Failed to create the NetEventSession: $_" -EA Stop) + } + + # + # add packet capture when -NoPackets not in use + # + if (-NOT $NoPackets.IsPresent) { + Write-Log "Adding packet capture." + Add-NetEventPacketCaptureProvider -SessionName $sessionName -CaptureType BothPhysicalAndSwitch -Level 5 -TruncationLength $snapLen | Out-Null + } + + # + # add ETW providers + # + foreach ($provider in $providers) { + try { + Write-Log "Adding $($provider.GUID) $(if ($provider.Name) {"($($provider.Name))"})" + Add-NetEventProvider -SessionName $sessionName -Name "{$($provider.GUID)}" -Level $provider.Level -MatchAnyKeyword $provider.MatchAnyKeyword -EA Stop | Out-Null + } catch { + Write-LogWarning "Could not add provider $($provider.GUID)`: $_" -Code "PROVIDER_ADD_FAILURE" + } + } + + # + # Start the session and optionally wait for the user to stop the session + # + Write-Log "Starting capture session." + try { + Start-NetEventSession $sessionName -EA Stop + Write-Log "Capture session successfully started." + } catch { + return (Write-LogError "Failed to start the NetEventSession: $_" -Code "TRACE_START_FAILURE") + } + + + # Prompt if -NoPrompt is not present + # Two negatives make a positive, it's the Microsoft way! + if (-NOT $NoPrompt.IsPresent) { + # repro the issue then press q to stop the trace + Write-Host -ForegroundColor Green "`n`The data collection has started.`n`nReproduce the issue now and then press 'q' to stop tracing.`n`n" + + do { + $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + } until ($x.Character -eq 'q') + + # stop tracing + Write-Log "Stopping $sessionName." + Stop-NetEventSession $sessionName | Out-Null + Remove-NetEventSession $sessionName | Out-Null + + # run collectlogs.ps1 when -CollectLogs set + if ( -NOT $NoLogs.IsPresent ) { + Write-Log "Trying to run collectlogs.ps1" + $BaseDir = "c:\k\debug" + + # make sure the dir is created + $null = mkdir $BaseDir -Force -EA SilentlyContinue + + # is collectlogs.ps1 in $baseDir? + $isCLFnd = Get-Item "$BaseDir\collectlogs.ps1" -EA SilentlyContinue + + if (-NOT $isCLFnd) { + Write-Log "Collectlogs.ps1 not found. Attempting to download." + # try to download collectlogs.ps1 + try { + $isCLFnd = Get-WebFile -URI 'https://raw.githubusercontent.com/microsoft/SDN/master/Kubernetes/windows/debug/collectlogs.ps1' -Path "$BaseDir" -FileName 'collectlogs.ps1' + } catch { + return (Write-LogWarning "The trace was successful but collectlogs failed to download: $_" -Code "COLLECTLOGS_DOWNLOAD_FAILED") + } + } else { + $isCLFnd = $isCLFnd.FullName + } + + # execute collectlogs.ps1 + if ($isCLFnd) { + Write-Host "Running collectlogs.ps1." + # redirecting as much of the collectlog output to the success stream for collection + $clResults = &$isCLFnd *>&1 | ForEach-Object ToString + } + } + + Write-Host -ForegroundColor Green "`n`nAll done! The data is located at:`n`t- $EtlFile $(if ($clResults) {"`n`t- $($clResults[-1].Substring(22))"})" + } else { + Write-Host -ForegroundColor Yellow "Use this command to stop capture: Stop-NetEventSession $sessionName" + Write-Host -ForegroundColor Yellow "Use this command to remove capture: Remove-NetEventSession $sessionName" + Write-Host -ForegroundColor Yellow "The data file will be located at $EtlFile." + } + + Write-Log "Process - End" +} + +end { + Write-Log "End - Begin" + + Write-Log "End - Work complete!" + + ############################################# + ### DO NOT USE libLogging past this point ### + ############################################# + Close-Logging -ModuleName "Start-SdnDebugTracing" + +} \ No newline at end of file diff --git a/Kubernetes/windows/debug/lib/libClass.psm1 b/Kubernetes/windows/debug/lib/libClass.psm1 new file mode 100644 index 00000000..b3ffd20c --- /dev/null +++ b/Kubernetes/windows/debug/lib/libClass.psm1 @@ -0,0 +1,79 @@ +# Data structure for ETW providers. +# This implementation requires the use of the ETW GUID. +# Everything else is optional with default values for level and keywords. + +using namespace System.Collections.Generic + +class Provider { + # [Optional w/ GUID] ETW name + [string]$Name + # [Optional w/ Name] ETW GUID - Recommended! ETW name doesn't always resolve properly, GUID always does. + [guid]$GUID + # [Optional] Logging level. Default = [byte]::MaxValue (0xff) + [byte]$Level + # [Optional] Logging keywords. Default = [UInt64]::MaxValue (0xffffffffffffffff) + [uint64]$MatchAnyKeyword + + # supported methods of creating a provider object + #region + + # all properties + Provider( + [string]$Name, + [guid]$GUID, + [byte]$Level, + [uint64]$MatchAnyKeyword + ) { + $this.Name = $Name + $this.GUID = $GUID + $this.Level = $level + $this.MatchAnyKeyword = $MatchAnyKeyword + } + + # all but the Name property + Provider( + [guid]$GUID, + [byte]$Level, + [uint64]$MatchAnyKeyword + ) { + $this.Name = "" + $this.GUID = $GUID + $this.Level = $level + $this.MatchAnyKeyword = $MatchAnyKeyword + } + + # GUID and level property + Provider( + [guid]$GUID, + [byte]$Level + ) { + $this.Name = "" + $this.GUID = $GUID + $this.Level = $level + $this.MatchAnyKeyword = [UInt64]::MaxValue + } + + # GUID, name, and level property + Provider( + [string]$Name, + [guid]$GUID, + [byte]$Level + ) { + $this.Name = $Name + $this.GUID = $GUID + $this.Level = $level + $this.MatchAnyKeyword = [UInt64]::MaxValue + } + + # only GUID + Provider( + [guid]$GUID + ) { + $this.Name = "" + $this.GUID = $GUID + $this.Level = [byte]::MaxValue + $this.MatchAnyKeyword = [UInt64]::MaxValue + } + + #endregion Provider() +} \ No newline at end of file diff --git a/Kubernetes/windows/debug/lib/libFunction.psm1 b/Kubernetes/windows/debug/lib/libFunction.psm1 new file mode 100644 index 00000000..6e8df035 --- /dev/null +++ b/Kubernetes/windows/debug/lib/libFunction.psm1 @@ -0,0 +1,204 @@ +using namespace System.Collections.Generic +using module .\libClass.psm1 + + +# Downloads a file from the Internet. +# Returns the full path to the download. +function Get-WebFile +{ + param ( + [string]$URI, + [string]$Path, + [string]$FileName + ) + + Write-Debug "Get-WebFile - Start." + + # validate path + if ( -NOT (Test-Path "$Path" -IsValid) ) { + return (Write-Error "The save path, $Path, is not valid. Error: $_" -EA Stop) + } + + # create the path if missing + if ( -NOT (Get-Item "$Path" -EA SilentlyContinue) ) { + try { + $null = mkdir "$Path" -Force -EA Stop + } catch { + return (Write-Error "The save path, $Path, does not exist and cannot be created. Error: $_" -EA Stop) + } + + } + + # create the full path + $OutFile = "$Path\$fileName" + + # use curl if it is found in the path + # options are iwr (Invoke-WebRequest (default)), bits (Start-BitsTransfer), and curl (preferred when found) + $dlMethods = "iwr", "curl", "bits" + $dlMethod = "iwr" + + # switch to curl when found + $curlFnd = Get-Command "curl.exe" -EA SilentlyContinue + if ($curlFnd) { $dlMethod = "curl" } + + Write-Verbose "Get-WebFile - Attempting download of $URI to $OutFile" + + # did the download work? + $dlWorked = $false + + # methods tried + # initialize with curl because if curl is found then we're using it, if it's not found then we shouldn't try it + $tried = @("curl") + + # loop through + do { + switch ($dlMethod) { + # tracks whether + "curl" { + Write-Verbose "Get-WebFile - Download with curl." + + Push-Location "$Path" + # download with curl + # -L = download location + # -o = output file + # -s = Silent + curl.exe -L $URI -o $OutFile -s + Pop-Location + } + + "iwr" { + Write-Verbose "Get-WebFile - Download with Invoke-WebRequest." + + # make sure we don't try to use an insecure SSL/TLS protocol when downloading files + Write-Debug "Get-WebFile - Disabling unsupported SSL/TLS protocls." + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12, [System.Net.SecurityProtocolType]::Tls13 + + # download silently with iwr + $oldProg = $global:ProgressPreference + $Global:ProgressPreference = "SilentlyContinue" + $null = Invoke-WebRequest -Uri $URI -OutFile "$OutFile" -MaximumRedirection 5 -PassThru + $Global:ProgressPreference = $oldProg + } + + "bits" { + Write-Verbose "Get-WebFile - Download with Start-BitsTransfer." + + # download silently with iwr + $oldProg = $global:ProgressPreference + $Global:ProgressPreference = "SilentlyContinue" + $null = Start-BitsTransfer -Source $URI -Destination "$OutFile" + $Global:ProgressPreference = $oldProg + } + + Default { return (Write-Error "An unknown download method was selected. This should not happen. dlMethod: $_" -EA Stop) } + } + + # is there a file, any file, then consider this a success + $dlFnd = Get-Item "$OutFile" -EA SilentlyContinue + + if ( -NOT $dlFnd ) { + # change download method and try again + Write-Verbose "Failed to download using $dlMethod." + + if ($tried.Count -lt $dlMethods.Count) { + if ($dlMethod -notin $tried) { + $tried += $dlMethod + Write-Verbose "Get-WebFile - Added $dlMethod to tried: $($tried -join ', ')" + } + + :dl foreach ($dl in $dlMethods) { + if ($dl -notin $tried) { + Write-Verbose "Get-WebFile - Switching to $dl method." + $dlMethod = $dl + $tried += $dl + break dl + } + } + } else { + return (Write-Error "The download has failed!" -EA Stop) + } + } else { + # exit the loop + $dlWorked = $true + } + } until ($dlWorked) + + Write-Verbose "Get-WebFile - File downloaded to $OutFile." + + #Add-Log "Downloaded successfully to: $output" + Write-Debug "Get-WebFile - Returning: $OutFile" + Write-Debug "Get-WebFile - End." + return $OutFile +} + + +function Import-HnsProfile { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $file + ) + + #Write-Log "Import-HnsProfile - " + Write-Log "Import-HnsProfile - Begin" + + # does the file exist? + $fileObj = Get-Item "$file" -EA SilentlyContinue + + if (-NOT $fileObj) { + throw "Failed to find the profile file: $file" + } + Write-Log "Import-HnsProfile - File found: $file" + + # is the file JSON? + if ($fileObj.Extension -ne ".json") { + throw "The profile file is invalid. The profile must be a JSON file. File extension found: $($fileObj.Extension)" + } + + # import the JSON + $profiles = Get-Content $fileObj | ConvertFrom-Json + + # make sure the profile layout is correct + if ($profiles.Count -le 0) { + throw "No profiles were found in the file." + } + + # the properties must be: + $propList = 'Name', 'GUID', 'Level', 'MatchAnyKeyword' | Sort-Object + $propFnd = $profiles | Get-Member -Type NoteProperty | ForEach-Object Name | Sort-Object + + $propValid = $true + foreach ($pl in $propList) { + if ($pl -notin $propFnd) { + Write-Warning "$pl was not found in the profile." + $propValid = $false + } + } + + if ($propValid -eq $false) { + throw "The profile object is invalid." + } + + # finally... create a Provider object + $props = [List[Provider]]::new() + + foreach ($p in $profiles) { + # try to create the Provider object + try { + [string]$Name = $p.Name + [guid]$GUID = $p.GUID + [byte]$Level = $p.Level + [uint64]$MatchAnyKeyword = $p.MatchAnyKeyword + + $tmpProv = [Provider]::new($Name, $GUID, $Level, $MatchAnyKeyword) + $props.Add($tmpProv) + + Remove-Variable tmpProv, Name, GUID, Level, MatchAnyKeyword -EA SilentlyContinue + } catch { + return ( Write-Error "Failed to convert the profile. Error: $_" -EA Stop ) + } + } + + return $props +} diff --git a/Kubernetes/windows/debug/lib/libLogging.psm1 b/Kubernetes/windows/debug/lib/libLogging.psm1 new file mode 100644 index 00000000..a7424c95 --- /dev/null +++ b/Kubernetes/windows/debug/lib/libLogging.psm1 @@ -0,0 +1,1180 @@ +# PowerShell logging module +#requires -Version 5.1 + +using namespace System.Collections +using namespace System.Collections.Generic +using namespace System.Collections.Concurrent + + + +<# + +TO-DO: + + +#> + + + +enum LogType { + main + warning + error +} + +class Logging { + ### PROPERTIES/CONSTRUCTOR ### + #region + + # All logged text goes into the main stream + [ConcurrentQueue[string]] + $MainStream + + # Warning text also goes into the warning stream + [ConcurrentQueue[string]] + $WarningStream + + # Error text also goes into the error stream + [ConcurrentQueue[string]] + $ErrorStream + + # Enforces no writing of the log events to file. + [bool] + $NoWrite + + # Where do the logs get written to? + # Provide the path where the three log files will be written to + [string] + $LogPath + + # Name of the module. + hidden + [string] + $Module + + hidden + [string] + $ParentModule + + # Name of the MainStream file + hidden + [string] + $MainFile + + # Name of the WarningStream file + hidden + [string] + $WarningFile + + # Name of the MainStream file + hidden + [string] + $ErrorFile + + # since the MainStream does some async writing the number of events in MainStream does not accurately reflect + # the total number of events added to MainStream + # this variable tracks the total number of events + hidden + [uint64] + $MainStreamTotal + + # prevents multiple writers from executing + hidden + [bool] + $Writing + + # blocks adding new entries to logs once Close() has been called + hidden + [bool] + $Closing + + #endregion + + ## CONSTRUCTOR ## + #region + + Logging() { + $this.MainStream = [ConcurrentQueue[string]]::new() + $this.WarningStream = [ConcurrentQueue[string]]::new() + $this.ErrorStream = [ConcurrentQueue[string]]::new() + + $this.MainStreamTotal = 0 + + # setup logging files + # use PWD for the LogPath + $this.LogPath = $PWD.Path + + # stream log file names + $stamp = $this.Filestamp() + $this.MainFile = "MainStream_$stamp`.log" + $this.WarningFile = "WarningStream_$stamp`.log" + $this.ErrorFile = "ErrorStream_$stamp`.log" + $this.Module = "Validate-SoQCertificate" + $this.Closing = $false + $this.Writing = $false + + # create the files + try { + # the PWD should exist... + $null = New-Item "$($this.LogPath)\$($this.MainFile)" -ItemType File -EA Stop + $null = New-Item "$($this.LogPath)\$($this.WarningFile)" -ItemType File -EA Stop + $null = New-Item "$($this.LogPath)\$($this.ErrorFile)" -ItemType File -EA Stop + + $this.NoWrite = $false + } catch { + Write-Error "Failed to create a logging file: $_" -EA Stop + $this.NoWrite = $true + } + } + + Logging([string]$loggingPath) { + $this.MainStream = [ConcurrentQueue[string]]::new() + $this.WarningStream = [ConcurrentQueue[string]]::new() + $this.ErrorStream = [ConcurrentQueue[string]]::new() + + $this.MainStreamTotal = 0 + + # setup logging files + # test logpath + if ( (Test-Path "$loggingPath" -IsValid) ) { + $this.LogPath = $loggingPath + } else { + $this.LogPath = $PWD.Path + } + + + # stream log file names + $stamp = $this.Filestamp() + $this.MainFile = "MainStream_$stamp`.log" + $this.WarningFile = "WarningStream_$stamp`.log" + $this.ErrorFile = "ErrorStream_$stamp`.log" + $this.Closing = $false + $this.Writing = $false + $this.Module = "Validate-SoQCertificate" + + # create the files + try { + # make sure the LogPath is there + $null = mkdir "$($this.LogPath)" -Force -EA Stop + + $null = New-Item "$($this.LogPath)\$($this.MainFile)" -ItemType File -Force -EA Stop + $null = New-Item "$($this.LogPath)\$($this.WarningFile)" -ItemType File -Force -EA Stop + $null = New-Item "$($this.LogPath)\$($this.ErrorFile)" -ItemType File -Force -EA Stop + } catch { + Write-Error "Failed to create a logging file: $_" -EA Stop + } + } + + Logging([bool]$writeToFile) { + $this.MainStream = [ConcurrentQueue[string]]::new() + $this.WarningStream = [ConcurrentQueue[string]]::new() + $this.ErrorStream = [ConcurrentQueue[string]]::new() + + $this.MainStreamTotal = 0 + + # setup logging files + # use PWD for the LogPath if writeToFile is $true + if ($writeToFile) { + $this.LogPath = $PWD.Path + + # stream log file names + $stamp = $this.Filestamp() + $this.MainFile = "MainStream_$stamp`.log" + $this.WarningFile = "WarningStream_$stamp`.log" + $this.ErrorFile = "ErrorStream_$stamp`.log" + + # create the files + try { + # the PWD should exist... + $null = New-Item "$($this.LogPath)\$($this.MainFile)" -ItemType File -EA Stop + $null = New-Item "$($this.LogPath)\$($this.WarningFile)" -ItemType File -EA Stop + $null = New-Item "$($this.LogPath)\$($this.ErrorFile)" -ItemType File -EA Stop + $this.NoWrite = $false + } catch { + Write-Error "Failed to create a logging file: $_" -EA Stop + $this.NoWrite = $true + } + } else { + Write-Verbose "No write mode" + $this.LogPath = $null + + $this.MainFile = $null + $this.WarningFile = $null + $this.ErrorFile = $null + $this.NoWrite = $true + } + + $this.Module = "Validate-SoQCertificate" + $this.Closing = $false + $this.Writing = $false + } + + Logging([string]$loggingPath, [string]$moduleName) { + $this.MainStream = [ConcurrentQueue[string]]::new() + $this.WarningStream = [ConcurrentQueue[string]]::new() + $this.ErrorStream = [ConcurrentQueue[string]]::new() + + $this.MainStreamTotal = 0 + + # setup logging files + # test logpath + if ( (Test-Path "$loggingPath" -IsValid) ) { + $this.LogPath = $loggingPath + } else { + $this.LogPath = $PWD.Path + } + + + # stream log file names + $stamp = $this.Filestamp() + $this.MainFile = "MainStream_$stamp`.log" + $this.WarningFile = "WarningStream_$stamp`.log" + $this.ErrorFile = "ErrorStream_$stamp`.log" + $this.Closing = $false + $this.Writing = $false + $this.Module = $moduleName + + # create the files + try { + # make sure the LogPath is there + $null = mkdir "$($this.LogPath)" -Force -EA Stop + + $null = New-Item "$($this.LogPath)\$($this.MainFile)" -ItemType File -Force -EA Stop + $null = New-Item "$($this.LogPath)\$($this.WarningFile)" -ItemType File -Force -EA Stop + $null = New-Item "$($this.LogPath)\$($this.ErrorFile)" -ItemType File -Force -EA Stop + + $this.NoWrite = $false + } catch { + Write-Error "Failed to create a logging file: $_" -EA Stop + $this.NoWrite = $true + } + } + + Logging([bool]$writeToFile, [string]$moduleName) { + $this.MainStream = [ConcurrentQueue[string]]::new() + $this.WarningStream = [ConcurrentQueue[string]]::new() + $this.ErrorStream = [ConcurrentQueue[string]]::new() + + $this.MainStreamTotal = 0 + + # setup logging files + # use PWD for the LogPath if writeToFile is $true + if ($writeToFile) { + $this.LogPath = $PWD.Path + + # stream log file names + $stamp = $this.Filestamp() + $this.MainFile = "MainStream_$stamp`.log" + $this.WarningFile = "WarningStream_$stamp`.log" + $this.ErrorFile = "ErrorStream_$stamp`.log" + + # create the files + try { + # the PWD should exist... + $null = New-Item "$($this.LogPath)\$($this.MainFile)" -ItemType File -EA Stop + $null = New-Item "$($this.LogPath)\$($this.WarningFile)" -ItemType File -EA Stop + $null = New-Item "$($this.LogPath)\$($this.ErrorFile)" -ItemType File -EA Stop + $this.NoWrite = $false + } catch { + Write-Error "Failed to create a logging file: $_" -EA Stop + $this.NoWrite = $true + } + } else { + Write-Verbose "No write mode" + $this.LogPath = $null + + $this.MainFile = $null + $this.WarningFile = $null + $this.ErrorFile = $null + $this.NoWrite = $true + } + + $this.Module = $moduleName + $this.Closing = $false + $this.Writing = $false + } + + #endregion + + + ### METHOD ### + #region + + ## NEW ## + #region + + # this version always terminates + NewError( + [string]$module, + [string]$function, + [string]$code, + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($module, $function, $code, $message, "error") + Write-Debug "txt: $txt" + + # add to the log + $this.AddError($txt) + + # create a formatted entry without ERROR: at the beginning, because Write-Error adds that + #$txt2 = $this.FormatEntry($module, $function, $code, $message, "main") + + $this.Close() + #Write-Error -Message $txt -ErrorAction Stop + throw $txt + } + } + + # this version optionally terminates + NewError( + [string]$module, + [string]$function, + [string]$code, + [string]$message, + [bool]$terminate + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($module, $function, $code, $message, "error") + Write-Debug "txt: $txt; terminating: $terminate" + + # add to the log + $this.AddError($txt) + + if ($terminate) { + $this.Close() + #Write-Error -Message $txt -ErrorAction Stop + throw $txt + } else { + # create a formatted entry without ERROR: at the beginning, because Write-Error adds that + $txt2 = $this.FormatEntry($module, $function, $code, $message, "main") + Write-Error -Message $txt2 + } + } + } + + # this version optionally terminates + NewError( + [string]$function, + [string]$code, + [string]$message, + [bool]$terminate + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($this.Module, $function, $code, $message, "error") + Write-Debug "txt: $txt; terminating: $terminate" + + # add to the log + $this.AddError($txt) + + if ($terminate) { + $this.Close() + #Write-Error -Message $txt -ErrorAction Stop + throw $txt + } else { + # create a formatted entry without ERROR: at the beginning, because Write-Error adds that + $txt2 = $this.FormatEntry($this.Module, $function, $code, $message, "main") + + Write-Error -Message $txt2 + } + } + } + + # this version optionally terminates, uses the default module, and does not need a function + NewError( + [string]$code, + [string]$message, + [bool]$terminate + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($this.Module, $null, $code, $message, "error") + Write-Debug "txt: $txt; terminating: $terminate" + + # add to the log + $this.AddError($txt) + + if ($terminate) { + $this.Close() + #Write-Error -Message $txt -ErrorAction Stop + throw $txt + } else { + # create a formatted entry without ERROR: at the beginning, because Write-Error adds that + $txt2 = $this.FormatEntry($this.Module, $null, $code, $message, "main") + + Write-Error -Message $txt2 + } + } + } + + # warnings never terminates + NewWarning( + [string]$module, + [string]$function, + [string]$code, + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($module, $function, $code, $message, "warning") + + # add to the log + $this.AddWarning($txt) + + # create a formatted entry without WARNING: at the beginning, because Write-Warning adds that + $txt2 = $this.FormatEntry($module, $function, $code, $message, "main") + + Write-Warning $txt2 + } + } + + NewWarning( + [string]$function, + [string]$code, + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($this.Module, $function, $code, $message, "warning") + + # add to the log + $this.AddWarning($txt) + + # create a formatted entry without WARNING: at the beginning, because Write-Warning adds that + $txt2 = $this.FormatEntry($this.Module, $function, $code, $message, "main") + + Write-Warning $txt2 + } + } + + # warnings never terminate, use default module, no function + NewWarning( + [string]$code, + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($this.Module, $null, $code, $message, "warning") + + # add to the log + $this.AddWarning($txt) + + # create a formatted entry without WARNING: at the beginning, because Write-Warning adds that + $txt2 = $this.FormatEntry($this.Module, $null, $code, $message, "main") + + Write-Warning $txt2 + } + } + + # logging never terminates + NewLog( + [string]$module, + [string]$function, + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($module, $function, "", $message, "main") + + # add to the log + $this.AddLog($txt) + + # dump events to disk if there are more than 10000 lines in MainStream + if ( $this.MainStream.Count -ge 10 ) { + $this.UpdateLogFile() + } + } + } + + NewLog( + [string]$function, + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($this.Module, $function, "", $message, "main") + + # add to the log + $this.AddLog($txt) + + # dump events to disk if there are more than 10000 lines in MainStream + if ( $this.MainStream.Count -ge 10 ) { + $this.UpdateLogFile() + } + } + } + + NewLog( + [string]$message + ) { + if ( -NOT $this.Closing) { + # get the formatted entry + $txt = $this.FormatEntry($this.Module, $null, "", $message, "main") + + # add to the log + $this.AddLog($txt) + + # dump events to disk if there are more than 10000 lines in MainStream + if ( $this.MainStream.Count -ge 10 ) { + $this.UpdateLogFile() + } + } + } + + #endregion NEW + + ## ADD ## + #region + + # adds an event to a logging stream + # no terminating errors come from here + # don't use AddLog inside of AddLog + AddLog([string]$txt) { + if ( -NOT [string]::IsNullOrEmpty($txt) ) { + Write-Verbose "$txt" + + if ( -NOT $this.NoWrite ) { + $txt = "$($this.Timestamp())`: $txt" + $this.IncrementMainStream() + $this.MainStream.Enqueue($txt) + } + } + } + + AddLog([string]$txt, [bool]$Timestamp) { + if ( -NOT [string]::IsNullOrEmpty($txt) ) { + Write-Verbose "$txt" + + if ( -NOT $this.NoWrite ) { + if ( $Timestamp -eq $true ) { + $txt = "$($this.Timestamp())`: $txt" + } + + $this.IncrementMainStream() + $this.MainStream.Enqueue($txt) + } + } + } + + # non-terminating + hidden + AddWarning([string]$txt) { + if ( -NOT [string]::IsNullOrEmpty($txt) ) { + $txt = "$($this.Timestamp())`: $txt" + + $this.AddLog($txt, $false) + + if ( -NOT $this.NoWrite ) { $this.WarningStream.Enqueue($txt) } + } + } + + # always terminates when calling this method + hidden + AddError([string]$txt) { + if ( -NOT [string]::IsNullOrEmpty($txt) ) { + $txt = "$($this.Timestamp())`: $txt" + + $this.AddLog($txt, $false) + + if ( -NOT $this.NoWrite ) { $this.ErrorStream.Enqueue($txt) } + } + } + + #endregion + + ## WRITE ## + #region + + # !!! DO NOT call NewError, NewWarning, or NewLog in these methods !!! + # Use Write-Verbose cmdlets if troubleshooting logging is needed. + + # dumps events from the mainstream to file for up to ~250ms or no more entries + hidden + UpdateLogFile() { + <# + Write only when: + + 1. A file write is not in progress ($this.Writing -eq $false). + -AND- + 2. There is something to write ( -and $this.MainStream.Count -gt 0). + -AND- + 3. Writing is enabled ( -and -NOT $this.NoWrite). + + #> + if ( $this.Writing -eq $false -and $this.MainStream.Count -gt 0 -and -NOT $this.NoWrite ) { + # prevent overlapping writes by setting Writing to true - simple "lock" mechanism + $this.Writing = $true + + # create the parsed file and stream writer + $stream = [System.IO.StreamWriter]::new("$($this.LogPath)\$($this.MainFile)", $true) + + # dequeues an object to write it to file + # only spend ~250ms writing, max, to prevent noticeable hangs + $sw = [System.Diagnostics.Stopwatch]::new() + $sw.Start() + while ( $this.MainStream.Count -gt 0 -and $sw.ElapsedMilliseconds -lt 275 ) { + $line = "" + + # TryDequeue returns $true when the first element in the ConcurrentQueue was successfully dequeued to $line + if ( $this.MainStream.TryDequeue([ref]$line) ) { + # write the line to file + $stream.WriteLine( $line ) + } + } + + # stop the stopwatch + $sw.Stop() + + # close the StreamWriter + $stream.Close() + + # allow writing + $this.Writing = $false + } + } + + # writes all MainStream events to file - used by Close() + hidden + WriteLog2File() { + + if ($this.MainStream.Count -gt 0) { + $logFile = "$($this.LogPath)\$($this.MainFile)" + + # instance is closing so lock all other writing while the MainStream is written to file + $this.Writing = $true + $stream = [System.IO.StreamWriter]::new($logFile, $true) + + while ( $this.MainStream.Count -gt 0 ) { + $line = "" + + # TryDequeue returns $true when the first element in the ConcurrentQueue was successfully dequeued to $line + if ( $this.MainStream.TryDequeue([ref]$line) ) { + # write the line to file + $stream.WriteLine( $line ) + } + } + + # close the StreamWriter + $stream.Close() + } + } + + # writes all WarningStream events to file - used by Close() + hidden + WriteWarningLog2File() { + if ($this.WarningStream.Count -gt 0) { + $warnFile = "$($this.LogPath)\$($this.WarningFile)" + + $stream = [System.IO.StreamWriter]::new("$warnFile") + + while ( $this.WarningStream.Count -gt 0 ) { + $line = "" + + # TryDequeue returns $true when the first element in the ConcurrentQueue was successfully dequeued to $line + if ( $this.WarningStream.TryDequeue([ref]$line) ) { + # write the line to file + $stream.WriteLine( $line ) + } + } + + # close the StreamWriter + $stream.Close() + } + } + + # writes all ErrorStream events to file - used by Close() + hidden + WriteErrorLog2File() { + if ($this.ErrorStream.Count -gt 0) { + $errFile = "$($this.LogPath)\$($this.ErrorFile)" + + $stream = [System.IO.StreamWriter]::new("$errFile") + + while ( $this.ErrorStream.Count -gt 0 ) { + $line = "" + + # TryDequeue returns $true when the first element in the ConcurrentQueue was successfully dequeued to $line + if ( $this.ErrorStream.TryDequeue([ref]$line) ) { + # write the line to file + $stream.WriteLine( $line ) + } + } + + # close the StreamWriter + $stream.Close() + } + } + + #endregion WRITE + + ## UTILITY ## + #region + + # get a timestamp + [string] + hidden + Timestamp() { + return (Get-Date -Format "yyyyMMdd-HH:mm:ss.ffffff") + } + + # get a timestamp for a file name + [string] + hidden + Filestamp() { + return (Get-Date -Format "yyyyMMdd_HHmmss") + } + + IncrementMainStream() { + $this.MainStreamTotal = $this.MainStreamTotal + 1 + } + + [string] + hidden + FormatEntry( + [string]$mod, + [string]$function, + [string]$code, + [string]$message, + [LogType]$logType + ) { + $str = "" + + # modules with a dash (-) are treated as cmdlet names, and not class related modules + if ($mod -match '-') { + $modIsFunc = $true + } else { + $modIsFunc = $false + } + + # when the module is null and the function contains something, swap the two + if (-NOT [string]::IsNullOrEmpty($function) -and [string]::IsNullOrEmpty($mod)) { + $mod = $function + $function = $null + } + + # there must always be a module + switch ($logType) { + "error" { + # do not wrap in [] if the module name contains a dash (-)... assume this is a function + if ($modIsFunc) { + $str = "ERROR: $mod" + } else { + $str = "ERROR: [$mod]" + } + } + + "warning" { + if ($modIsFunc) { + $str = "WARNING: $mod" + } else { + $str = "WARNING: [$mod]" + } + } + + default { + if ($modIsFunc) { + $str = "$mod" + } else { + $str = "[$mod]" + } + + } + } + #Write-Host "2 - mod: $module, func: $function, code: $code, mess: $message, type: $logtype, str: $str" + + # function is options + if ( -NOT [string]::IsNullOrEmpty($function) -and -NOT $modIsFunc) { + $str = [string]::Concat($str, ".$function - ") + } elseif ( -NOT [string]::IsNullOrEmpty($function) -and $modIsFunc ) { + $str = [string]::Concat($str, " - [$function] - ") + } else { + $str = [string]::Concat($str, " - ") + } + + # add the message (not optional) + $str = [string]::Concat($str, $message) + + # code is optional + if ( -NOT [string]::IsNullOrEmpty($code) ) { + $str = [string]::Concat($str, " code: $code") + } + return $str + } + + Close() { + # set Closing to $true + $this.Closing = $true + + # wait for 100ms to make sure any outstanding work is completed + Start-Sleep -Milliseconds 100 + + # are there outstanding writes? + if ( $this.Writing ) { + $sw = [System.Diagnostics.Stopwatch]::new() + $sw.Start() + + do { + Start-Sleep -Milliseconds 50 + } until ($this.Writing -eq $false -or $sw.ElapsedMilliseconds -gt 500) + + # if still Writing then the StreamWriter may have experiences a failure + # rename the MainFile and continue writing the events to the alternate file + if ( $this.Writing ) { + $this.MainFile = "$($this.MainFile.Split('.')[0])_StreamFailure.log" + $null = New-Item "$($this.LogPath)\$($this.MainFile)" -ItemType File -Force + } + } + + # Write all the logs to file + # now handled by the Clear() method + + # clear the log data + $this.Clear() + + # clean up 0B files + $logFileobj = Get-Item "$($this.LogPath)\$($this.MainFile)" -EA SilentlyContinue + $errFileObj = Get-Item "$($this.LogPath)\$($this.ErrorFile)" -EA SilentlyContinue + $warnFileObj = Get-Item "$($this.LogPath)\$($this.WarningFile)" -EA SilentlyContinue + + if ( $logFileobj.Length -eq 0 ) { Remove-Item $logFileobj -Force -EA SilentlyContinue } + if ( $errFileObj.Length -eq 0 ) { Remove-Item $errFileObj -Force -EA SilentlyContinue } + if ( $warnFileObj.Length -eq 0 ) { Remove-Item $warnFileObj -Force -EA SilentlyContinue } + + # set all variables to $null + $this.MainFile = $null + $this.MainStream = $null + + $this.WarningFile = $null + $this.WarningStream = $null + + $this.ErrorFile = $null + $this.ErrorStream = $null + + $this.Closing = $null + $this.Writing = $null + + $this.LogPath = $null + } + + Clear() { + # clear all the streams by dequeing everything with the write log methods + # the Clear() method for ConcurrentQueue does not work on PowerShell 5.1/.NET 4.8.1, so this acts as a workaround and a way to prevent log loss rolled into one + if ( -NOT $this.NoWrite ) { + $this.AddLog("[Logging].Clear - Writing logs to file.") + $this.WriteErrorLog2File() + $this.WriteWarningLog2File() + $this.WriteLog2File() + } + + $this.MainStreamTotal = 0 + } + #endregion UTILITY + + #endregion METHODS +} + +#region +$TypeData = @{ + TypeName = 'Logging' + MemberType = 'ScriptProperty' + MemberName = 'MainCount' + Value = {$this.MainStreamTotal} +} + +Update-TypeData @TypeData -EA SilentlyContinue + +$TypeData = @{ + TypeName = 'Logging' + MemberType = 'ScriptProperty' + MemberName = 'WarningCount' + Value = {$this.WarningStream.Count} +} + +Update-TypeData @TypeData -EA SilentlyContinue + +$TypeData = @{ + TypeName = 'Logging' + MemberType = 'ScriptProperty' + MemberName = 'ErrorCount' + Value = {$this.ErrorStream.Count} +} + +Update-TypeData @TypeData -EA SilentlyContinue + + +$TypeData = @{ + TypeName = 'Logging' + DefaultDisplayPropertySet = 'MainCount', 'WarningCount', 'ErrorCount', 'LogPath' +} + +Update-TypeData @TypeData -EA SilentlyContinue +#endregion + +#endregion LOGGING + +<# + $script:libLogging.NewLog("") + $script:libLogging.NewLog("module", function", "message") + $script:libLogging.NewLog("function", "message") + $script:libLogging.NewError("code", "", $false) + $script:libLogging.NewWarning("code", "") +#> + +function script:Start-Logging { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] + $ModuleName, + + [Parameter()] + [string] + $LogPath = $null + ) + + Write-Verbose "Start-Logging - ModuleName: $ModuleName" + Write-Verbose "Start-Logging - LogPath: $LogPath" + # do not write the log unless LogPath has a valid path + if ( [string]::IsNullOrEmpty($LogPath) ) { + Write-Verbose "No log write mode." + + # change the module if the log var exists + if ($script:libLogging) { + $oldLogMod = $script:libLogging.Module + Write-Verbose "oldLogMod: $oldLogMod" + $script:libLogging.Module = $moduleName + # otherwise create a new log + } else { + Write-Verbose "New log file." + $oldLogMod = $null + # create new log with NoWrite set to $true + $script:libLogging = [Logging]::new($false, $moduleName) + $script:libLogging.ParentModule = $ModuleName + $script:libLogging.NewLog("Start-Logging - Parent module: $($script:libLogging.ParentModule)") + + } + } else { + Write-Verbose "Write logs to path: $LogPath" + + # LogPath must be a directory + $lpIsDir = Get-Item "$LogPath" -EA SilentlyContinue + if ( $lpIsDir -and -NOT $lpIsDir.PSIsContainer ) { $LogPath = $PWD.Path } + + # create the dir if needed + try { + $null = New-Item "$LogPath" -ItemType Directory -Force -EA Stop + } catch { + # use PWD instead + $LogPath = $PWD.Path + } + + if ($logFnd) { + $oldLogMod = $script:libLogging.Module + $script:libLogging.Module = $moduleName + # otherwise create a new log + } else { + $oldLogMod = "" + # create new log with NoWrite set to $true + $script:libLogging = [Logging]::new($LogPath, $moduleName) + $script:libLogging.ParentModule = $ModuleName + $script:libLogging.NewLog("Start-Logging - Parent module: $($script:libLogging.ParentModule)") + } + } + + return $oldLogMod +} + +function script:Close-Logging { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] + $ModuleName, + + [Parameter()] + [string] + $oldLogMod = $null + ) + + # close the log if the parent module calls Close-Logging + $script:libLogging.NewLog("Close-Logging - ModuleName: $ModuleName; Parent: $($script:libLogging.ParentModule)") + $script:libLogging.NewLog("Close-Logging - oldLogMod: $oldLogMod") + if ( $ModuleName -eq $script:libLogging.ParentModule ) { # -or [string]::IsNullOrEmpty($oldLogMod) + $script:libLogging.NewLog("Close-Logging - Closing log.") + $script:libLogging.Close() + # swap module name back when returning to a caller + } else { + $script:libLogging.NewLog("Close-Logging - Change log module back to $oldLogMod") + $script:libLogging.Module = $oldLogMod + } + +} + +function script:Write-Log { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] + $Text, + + [Parameter()] + [string] + $Module = $null, + + [Parameter()] + [string] + $Function = $null + ) + + # most of the work is handled by the class. + # proceed only if there's something to log and let the class figure out the rest. + if ( -NOT [string]::IsNullOrEmpty($Text) -and -NOT [string]::IsNullOrWhiteSpace($Text) ) { + # write with module and function from args when module and function are not null/empty + if ( -NOT [string]::IsNullOrEmpty($Module) -and -NOT [string]::IsNullOrEmpty($Function) ) { + $script:libLogging.NewLog($Text, $Function, $Module) + # write with the function + } elseif ( [string]::IsNullOrEmpty($Module) -and -NOT [string]::IsNullOrEmpty($Function) ) { + $script:libLogging.NewLog($Text, $Function) + # write just the text + } else { + $script:libLogging.NewLog($Text) + } + } else { + Write-Debug "Write-Log - No text passed." + } +} + + +<# + +# warnings never terminate, use default module, no function + NewWarning( + [string]$code, + [string]$message + ) + + NewWarning( + [string]$module, + [string]$function, + [string]$code, + [string]$message + ) + + NewWarning( + [string]$function, + [string]$code, + [string]$message + ) + +#> +function script:Write-LogWarning { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] + $Text, + + [Parameter(Mandatory)] + [string] + $Code, + + [Parameter()] + [string] + $Module = $null, + + [Parameter()] + [string] + $Function = $null + ) + + # most of the work is handled by the class. + # proceed only if there's something to log and let the class figure out the rest. + if ( (-NOT [string]::IsNullOrEmpty($Text) -and -NOT [string]::IsNullOrWhiteSpace($Text)) -and + (-NOT [string]::IsNullOrEmpty($Code) -and -NOT [string]::IsNullOrWhiteSpace($Code)) ) { + + # write with module and function from args when module and function are not null/empty + if ( -NOT [string]::IsNullOrEmpty($Module) -and -NOT [string]::IsNullOrEmpty($Function) ) { + $script:libLogging.NewWarning($Module, $Function, $Code, $Text) + # write with the function + } elseif ( [string]::IsNullOrEmpty($Module) -and -NOT [string]::IsNullOrEmpty($Function) ) { + $script:libLogging.NewWarning($Function, $Code, $Text) + # write just the text + } else { + $script:libLogging.NewWarning($Code, $Text) + } + } else { + Write-Debug "Write-LogWarning - No text or code passed. Text: $Text; Code: $Code" + } +} + + +<# +# this version always terminates + NewError( + [string]$code, + [string]$message, + [bool]$terminate + ) + + NewError( + [string]$function, + [string]$code, + [string]$message, + [bool]$terminate + ) + + NewError( + [string]$module, + [string]$function, + [string]$code, + [string]$message, + [bool]$terminate + ) + +#> +function script:Write-LogError { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] + $Text, + + [Parameter(Mandatory)] + [string] + $Code, + + [Parameter()] + [string] + $Module = $null, + + [Parameter()] + [string] + $Function = $null, + + [Parameter()] + [switch] + $NonTerminating + ) + + # most of the work is handled by the class. + # proceed only if there's something to log and let the class figure out the rest. + if ( (-NOT [string]::IsNullOrEmpty($Text) -and -NOT [string]::IsNullOrWhiteSpace($Text)) -and + (-NOT [string]::IsNullOrEmpty($Code) -and -NOT [string]::IsNullOrWhiteSpace($Code)) ) { + + # write with module and function from args when module and function are not null/empty + if ( -NOT [string]::IsNullOrEmpty($Module) -and -NOT [string]::IsNullOrEmpty($Function) ) { + $script:libLogging.NewError($Module, $Function, $Code, $Text, !$NonTerminating.IsPresent) + # write with the function + } elseif ( [string]::IsNullOrEmpty($Module) -and -NOT [string]::IsNullOrEmpty($Function) ) { + $script:libLogging.NewError($Function, $Code, $Text, !$NonTerminating.IsPresent) + # write just the text + } else { + $script:libLogging.NewError($Code, $Text, !$NonTerminating.IsPresent) + } + } else { + Write-Debug "Write-LogError - No text or code passed. Text: $Text; Code: $Code" + } +} diff --git a/Kubernetes/windows/debug/profiles/hns_full.json b/Kubernetes/windows/debug/profiles/hns_full.json new file mode 100644 index 00000000..6d248d6b --- /dev/null +++ b/Kubernetes/windows/debug/profiles/hns_full.json @@ -0,0 +1,140 @@ +[ + { + "Name": "", + "GUID": "564368d6-577b-4af5-ad84-1c54464848e6", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "0c885e0d-6eb6-476c-a048-2457eed3a5c1", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "80ce50de-d264-4581-950d-abadeee0d340", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "d0e4bc17-34c7-43fc-9a72-d89a59d6979a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "93f693dc-9163-4dee-af64-d855218af242", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "6c28c7e5-331b-4437-9c69-5352a2f7f296", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "5eefebdb-e90c-423a-8abf-0241e7c5b87d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "d1bc9aff-2abf-4d71-9146-ecb2a986eb85", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "0c478c5b-0351-41b1-8c58-4a6737da32e3", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "2f07e2ee-15db-40f1-90ef-9d7ba282188a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "eb004a05-9b1a-11d4-9123-0050047759bc", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "1c95126e-7eea-49a9-a3fe-a378b03ddb4d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "15a7a4f8-0072-4eab-abad-f98a4d666aed", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "6a1f2b00-6a90-4c38-95a5-5cab3b056778", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "66c07ecd-6667-43fc-93f8-05cf07f446ec", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "aa7387cf-3639-496a-b3bf-dc1e79a6fc5a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "ae3f6c6d-bf2a-4291-9d07-59e661274ee3", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "9b322459-4ad9-4f81-8eea-dc77cdd18ca6", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "a6f32731-9a38-4159-a220-3d9b7fc5fe5d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "1f387cbc-6818-4530-9db6-5f1058cd7e86", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "67dc0d66-3695-47c0-9642-33f76f7bd7ad", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "94deb9d1-0a52-449b-b368-41e4426b4f36", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "9f2660ea-cfe7-428f-9850-aeca612619b0", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + } +] diff --git a/Kubernetes/windows/debug/profiles/hns_lite.json b/Kubernetes/windows/debug/profiles/hns_lite.json new file mode 100644 index 00000000..3ad25b7e --- /dev/null +++ b/Kubernetes/windows/debug/profiles/hns_lite.json @@ -0,0 +1,80 @@ +[ + { + "Name": "", + "GUID": "564368d6-577b-4af5-ad84-1c54464848e6", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "0c885e0d-6eb6-476c-a048-2457eed3a5c1", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "80ce50de-d264-4581-950d-abadeee0d340", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "9d911ddb-d45f-41c3-b766-d566d2655c4a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "d0e4bc17-34c7-43fc-9a72-d89a59d6979a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "93f693dc-9163-4dee-af64-d855218af242", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "a6f32731-9a38-4159-a220-3d9b7fc5fe5d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "6c28c7e5-331b-4437-9c69-5352a2f7f296", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "c29c4fb7-b60e-4fff-9af9-cf21f9b09a34", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "1f387cbc-6818-4530-9db6-5f1058cd7e86", + "Level": 6, + "MatchAnyKeyword": 4292870139 + }, + { + "Name": "", + "GUID": "67dc0d66-3695-47c0-9642-33f76f7bd7ad", + "Level": 6, + "MatchAnyKeyword": 4294967261 + }, + { + "Name": "", + "GUID": "94deb9d1-0a52-449b-b368-41e4426b4f36", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "9f2660ea-cfe7-428f-9850-aeca612619b0", + "Level": 6, + "MatchAnyKeyword": 4259840 + } +] diff --git a/Kubernetes/windows/debug/profiles/hns_normal.json b/Kubernetes/windows/debug/profiles/hns_normal.json new file mode 100644 index 00000000..5c17fe97 --- /dev/null +++ b/Kubernetes/windows/debug/profiles/hns_normal.json @@ -0,0 +1,128 @@ +[ + { + "Name": "", + "GUID": "564368d6-577b-4af5-ad84-1c54464848e6", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "0c885e0d-6eb6-476c-a048-2457eed3a5c1", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "80ce50de-d264-4581-950d-abadeee0d340", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "d0e4bc17-34c7-43fc-9a72-d89a59d6979a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "93f693dc-9163-4dee-af64-d855218af242", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "6c28c7e5-331b-4437-9c69-5352a2f7f296", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "5eefebdb-e90c-423a-8abf-0241e7c5b87d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "d1bc9aff-2abf-4d71-9146-ecb2a986eb85", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "2f07e2ee-15db-40f1-90ef-9d7ba282188a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "1c95126e-7eea-49a9-a3fe-a378b03ddb4d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "15a7a4f8-0072-4eab-abad-f98a4d666aed", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "6a1f2b00-6a90-4c38-95a5-5cab3b056778", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "66c07ecd-6667-43fc-93f8-05cf07f446ec", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "aa7387cf-3639-496a-b3bf-dc1e79a6fc5a", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "ae3f6c6d-bf2a-4291-9d07-59e661274ee3", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "9b322459-4ad9-4f81-8eea-dc77cdd18ca6", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "a6f32731-9a38-4159-a220-3d9b7fc5fe5d", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "1f387cbc-6818-4530-9db6-5f1058cd7e86", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "67dc0d66-3695-47c0-9642-33f76f7bd7ad", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "94deb9d1-0a52-449b-b368-41e4426b4f36", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + }, + { + "Name": "", + "GUID": "9f2660ea-cfe7-428f-9850-aeca612619b0", + "Level": 6, + "MatchAnyKeyword": 18446744073709551615 + } +] diff --git a/Kubernetes/windows/debug/starthnstrace.ps1 b/Kubernetes/windows/debug/starthnstrace.ps1 index 22e9060e..004c747c 100644 --- a/Kubernetes/windows/debug/starthnstrace.ps1 +++ b/Kubernetes/windows/debug/starthnstrace.ps1 @@ -181,13 +181,10 @@ function Get-WebFile [Provider]::New('{A6F32731-9A38-4159-A220-3D9B7FC5FE5D}',6), # Microsoft-Windows-SharedAccess_NAT [Provider]::New('{6C28C7E5-331B-4437-9C69-5352A2F7F296}', 6), # Microsoft.Windows.Hyper.V.VmsIf [Provider]::New('{C29C4FB7-B60E-4FFF-9AF9-CF21F9B09A34}', 6), # Microsoft-Windows-Hyper-V-SynthNic - # VmSwitch Enable ETW and WPP Events - Control Path Only - [Provider]::New('{1F387CBC-6818-4530-9DB6-5F1058CD7E86}', 6, 4292870139), # vmswitch - 0xFFDFFFFB + [Provider]::New('{1F387CBC-6818-4530-9DB6-5F1058CD7E86}', 6, 4292870139), # vmswitch - 0xFFDFFFFB # VmSwitch Enable ETW and WPP Events - Control Path Only [Provider]::New('{67DC0D66-3695-47c0-9642-33F76F7BD7AD}', 6, 4294967261), # Microsoft-Windows-Hyper-V-VmSwitch - 0xFFFFFFDD - # available starting in build 19041. Safe to add here since the try-catch will silently fail if ETW not present - [Provider]::New('{94DEB9D1-0A52-449B-B368-41E4426B4F36}', 6), # Microsoft.Windows.Hyper.V.NetSetupHelper - # VFPEXT is an optional component - [Provider]::New('{9F2660EA-CFE7-428F-9850-AECA612619B0}', 6, 4259840) # Microsoft-Windows-Hyper-V-VfpExt - 0x00410000 + [Provider]::New('{94DEB9D1-0A52-449B-B368-41E4426B4F36}', 6), # Microsoft.Windows.Hyper.V.NetSetupHelper # available starting in build 19041. Safe to add here since the try-catch will silently fail if ETW not present + [Provider]::New('{9F2660EA-CFE7-428F-9850-AECA612619B0}', 6, 4259840) # Microsoft-Windows-Hyper-V-VfpExt - 0x00410000 # VFPEXT is an optional component # capture name $sessionName = 'HnsCapture' diff --git a/Kubernetes/windows/debug/startpacketcapture.ps1 b/Kubernetes/windows/debug/startpacketcapture.ps1 index 51c3d8a5..5a4ece83 100644 --- a/Kubernetes/windows/debug/startpacketcapture.ps1 +++ b/Kubernetes/windows/debug/startpacketcapture.ps1 @@ -28,141 +28,6 @@ param $CollectLogs ) -### CLASSES AND FUNCTIONS ### -#region - - -# Data structure for ETW providers. -# This implementation requires the use of the ETW GUID. -# Everything else is optional with default values for level and keywords. -class Provider -{ - # [Optional w/ GUID] ETW name - [string]$Name - # [Optional w/ Name] ETW GUID - Recommended! ETW name doesn't always resolve properly, GUID always does. - [guid]$GUID - # [Optional] Logging level. Default = [byte]::MaxValue (0xff) - [byte]$Level - # [Optional] Logging keywords. Default = [UInt64]::MaxValue (0xffffffffffffffff) - [uint64]$MatchAnyKeyword - - # supported methods of creating a provider object - #region - - # all properties - Provider( - [string]$Name, - [guid]$GUID, - [byte]$Level, - [uint64]$MatchAnyKeyword - ) - { - $this.Name = $Name - $this.GUID = $GUID - $this.Level = $level - $this.MatchAnyKeyword = $MatchAnyKeyword - } - - # all but the Name property - Provider( - [guid]$GUID, - [byte]$Level, - [uint64]$MatchAnyKeyword - ) - { - $this.Name = "" - $this.GUID = $GUID - $this.Level = $level - $this.MatchAnyKeyword = $MatchAnyKeyword - } - - # GUID and level property - Provider( - [guid]$GUID, - [byte]$Level - ) - { - $this.Name = "" - $this.GUID = $GUID - $this.Level = $level - $this.MatchAnyKeyword = [UInt64]::MaxValue - } - - # GUID, name, and level property - Provider( - [string]$Name, - [guid]$GUID, - [byte]$Level - ) - { - $this.Name = $Name - $this.GUID = $GUID - $this.Level = $level - $this.MatchAnyKeyword = [UInt64]::MaxValue - } - - # only GUID - Provider( - [guid]$GUID - ) - { - $this.Name = "" - $this.GUID = $GUID - $this.Level = [byte]::MaxValue - $this.MatchAnyKeyword = [UInt64]::MaxValue - } - - #endregion Provider() -} - - -# Downloads a file from the Internet. -# Returns the full path to the download. -function Get-WebFile -{ - param ( - [string]$URI, - [string]$savePath, - [string]$fileName - ) - - Write-Debug "Get-WebFile - Start." - # make sure we don't try to use an insecure SSL/TLS protocol when downloading files - Write-Debug "Get-WebFile - Disabling unsupported SSL/TLS protocls." - $secureProtocols = @() - $insecureProtocols = @( [System.Net.SecurityProtocolType]::SystemDefault, - [System.Net.SecurityProtocolType]::Ssl3, - [System.Net.SecurityProtocolType]::Tls, - [System.Net.SecurityProtocolType]::Tls11) - foreach ($protocol in [System.Enum]::GetValues([System.Net.SecurityProtocolType])) - { - if ($insecureProtocols -notcontains $protocol) - { - $secureProtocols += $protocol - } - } - [System.Net.ServicePointManager]::SecurityProtocol = $secureProtocols - - Write-Verbose "Get-WebFile - Attempting download of $URI." - try - { - Invoke-WebRequest -Uri $URI -OutFile "$savePath\$fileName" -MaximumRedirection 5 -EA Stop - Write-Verbose "Get-WebFile - File downloaded to $savePath\$fileName." - } - catch - { - # return terminating error - return (Write-Error "Could not download $URI`: $_" -EA Stop) - } - - #Add-Log "Downloaded successfully to: $output" - Write-Debug "Get-WebFile - Returning: $savePath\$fileName " - Write-Debug "Get-WebFile - End." - return "$savePath\$fileName" -} - -#endregion CLASSES and FUNCTIONS - ### CONSTANTS and VARIABLES ### #region @@ -177,31 +42,53 @@ function Get-WebFile [Provider]::New('{80CE50DE-D264-4581-950D-ABADEEE0D340}', 6), # Microsoft.Windows.HyperV.Compute [Provider]::New('{D0E4BC17-34C7-43fc-9A72-D89A59D6979A}', 6), # Microsoft.Windows.HostNetworkingService.PrivateCloudPlugin [Provider]::New('{93f693dc-9163-4dee-af64-d855218af242}', 6), # Microsoft-Windows-Host-Network-Management - [Provider]::New('{6C28C7E5-331B-4437-9C69-5352A2F7F296}', 6), # Microsoft.Windows.Hyper.V.VmsIf - # Firewall - [Provider]::New('{5EEFEBDB-E90C-423a-8ABF-0241E7C5B87D}', 6), # Windows Firewall Service + [Provider]::New('{6C28C7E5-331B-4437-9C69-5352A2F7F296}', 6), # Microsoft.Windows.Hyper.V.VmsIf + [Provider]::New('{5EEFEBDB-E90C-423a-8ABF-0241E7C5B87D}', 6), # Windows Firewall Service # Firewall [Provider]::New('{D1BC9AFF-2ABF-4D71-9146-ECB2A986EB85}', 6), # Microsoft-Windows-Windows Firewall With Advanced Security - # Protocols - [Provider]::New('{2F07E2EE-15DB-40F1-90EF-9D7BA282188A}', 6), # Microsoft-Windows-TCPIP + [Provider]::New('{2F07E2EE-15DB-40F1-90EF-9D7BA282188A}', 6), # Microsoft-Windows-TCPIP # Protocols [Provider]::New('{1C95126E-7EEA-49A9-A3FE-A378B03DDB4D}', 6), # Microsoft-Windows-DNS-Client [Provider]::New('{15A7A4F8-0072-4EAB-ABAD-F98A4D666AED}', 6), # Microsoft-Windows-Dhcp-Client [Provider]::New('{6A1F2B00-6A90-4C38-95A5-5CAB3B056778}', 6), # Microsoft-Windows-DHCPv6-Client - # NAT - [Provider]::New('{66C07ECD-6667-43FC-93F8-05CF07F446EC}', 6), # Microsoft-Windows-WinNat + [Provider]::New('{66C07ECD-6667-43FC-93F8-05CF07F446EC}', 6), # Microsoft-Windows-WinNat # NAT [Provider]::New('{AA7387CF-3639-496A-B3BF-DC1E79A6fc5A}', 6), # WIN NAT WPP [Provider]::New('{AE3F6C6D-BF2A-4291-9D07-59E661274EE3}', 6), # IP NAT WPP - # Shared Access - [Provider]::New('{9B322459-4AD9-4F81-8EEA-DC77CDD18CA6}', 6), # Shared Access Service WPP Provider + [Provider]::New('{9B322459-4AD9-4F81-8EEA-DC77CDD18CA6}', 6), # Shared Access Service WPP Provider # Shared Access [Provider]::New('{A6F32731-9A38-4159-A220-3D9B7FC5FE5D}', 6), # Microsoft-Windows-SharedAccess_NAT - # VmSwitch Enable ETW and WPP Events - Control Path Only - [Provider]::New('{1F387CBC-6818-4530-9DB6-5F1058CD7E86}', 6), # vmswitch - 0xFFDFFFFB + [Provider]::New('{1F387CBC-6818-4530-9DB6-5F1058CD7E86}', 6), # vmswitch - 0xFFDFFFFB # VmSwitch Enable ETW and WPP Events - Control Path Only [Provider]::New('{67DC0D66-3695-47c0-9642-33F76F7BD7AD}', 6), # Microsoft-Windows-Hyper-V-VmSwitch - 0xFFFFFFDD - # available starting in build 19041. Safe to add here since the try-catch will silently fail if ETW not present - [Provider]::New('{94DEB9D1-0A52-449B-B368-41E4426B4F36}', 6), # Microsoft.Windows.Hyper.V.NetSetupHelper - # VFPEXT is an optional component - [Provider]::New('{9F2660EA-CFE7-428F-9850-AECA612619B0}', 6) # Microsoft-Windows-Hyper-V-VfpExt - 0x00410000 + [Provider]::New('{94DEB9D1-0A52-449B-B368-41E4426B4F36}', 6), # Microsoft.Windows.Hyper.V.NetSetupHelper # available starting in build 19041. Safe to add here since the try-catch will silently fail if ETW not present + [Provider]::New('{9F2660EA-CFE7-428F-9850-AECA612619B0}', 6) # Microsoft-Windows-Hyper-V-VfpExt - 0x00410000 # VFPEXT is an optional component +<# + +[Provider[]]$providers = [Provider]::New('{564368D6-577B-4af5-AD84-1C54464848E6}', 6), # Microsoft-Windows-Overlay-HNSPlugin + [Provider]::New('{0c885e0d-6eb6-476c-a048-2457eed3a5c1}', 6), # Microsoft-Windows-Host-Network-Service + [Provider]::New('{80CE50DE-D264-4581-950D-ABADEEE0D340}', 6), # Microsoft.Windows.HyperV.Compute + [Provider]::New('{D0E4BC17-34C7-43fc-9A72-D89A59D6979A}', 6), # Microsoft.Windows.HostNetworkingService.PrivateCloudPlugin + [Provider]::New('{93f693dc-9163-4dee-af64-d855218af242}', 6), # Microsoft-Windows-Host-Network-Management + [Provider]::New('{6C28C7E5-331B-4437-9C69-5352A2F7F296}', 6), # Microsoft.Windows.Hyper.V.VmsIf + [Provider]::New('{5EEFEBDB-E90C-423a-8ABF-0241E7C5B87D}', 6), # Windows Firewall Service # Firewall + [Provider]::New('{D1BC9AFF-2ABF-4D71-9146-ECB2A986EB85}', 6), # Microsoft-Windows-Windows Firewall With Advanced Security + [Provider]::New('{0C478C5B-0351-41B1-8C58-4A6737DA32E3}', 6), # Microsoft-Windows-WFP + [Provider]::New('{2F07E2EE-15DB-40F1-90EF-9D7BA282188A}', 6), # Microsoft-Windows-TCPIP # Protocols + [Provider]::New('{EB004A05-9B1A-11D4-9123-0050047759BC}', 6), # NETIO (TCPIP WPP) + [Provider]::New('{1C95126E-7EEA-49A9-A3FE-A378B03DDB4D}', 6), # Microsoft-Windows-DNS-Client + [Provider]::New('{15A7A4F8-0072-4EAB-ABAD-F98A4D666AED}', 6), # Microsoft-Windows-Dhcp-Client + [Provider]::New('{6A1F2B00-6A90-4C38-95A5-5CAB3B056778}', 6), # Microsoft-Windows-DHCPv6-Client + [Provider]::New('{66C07ECD-6667-43FC-93F8-05CF07F446EC}', 6), # Microsoft-Windows-WinNat # NAT + [Provider]::New('{AA7387CF-3639-496A-B3BF-DC1E79A6fc5A}', 6), # WIN NAT WPP + [Provider]::New('{AE3F6C6D-BF2A-4291-9D07-59E661274EE3}', 6), # IP NAT WPP + [Provider]::New('{9B322459-4AD9-4F81-8EEA-DC77CDD18CA6}', 6), # Shared Access Service WPP Provider # Shared Access + [Provider]::New('{A6F32731-9A38-4159-A220-3D9B7FC5FE5D}', 6), # Microsoft-Windows-SharedAccess_NAT + [Provider]::New('{1F387CBC-6818-4530-9DB6-5F1058CD7E86}', 6), # vmswitch - 0xFFDFFFFB # VmSwitch Enable ETW and WPP Events - Control Path Only + [Provider]::New('{67DC0D66-3695-47c0-9642-33F76F7BD7AD}', 6), # Microsoft-Windows-Hyper-V-VmSwitch - 0xFFFFFFDD + [Provider]::New('{94DEB9D1-0A52-449B-B368-41E4426B4F36}', 6), # Microsoft.Windows.Hyper.V.NetSetupHelper # available starting in build 19041. Safe to add here since the try-catch will silently fail if ETW not present + [Provider]::New('{9F2660EA-CFE7-428F-9850-AECA612619B0}', 6) # Microsoft-Windows-Hyper-V-VfpExt - 0x00410000 # VFPEXT is an optional component + + +#> + # capture name $sessionName = 'HnsPacketCapture' @@ -330,4 +217,4 @@ else Write-Host -ForegroundColor Yellow "Use this command to stop capture: Stop-NetEventSession $sessionName" Write-Host -ForegroundColor Yellow "Use this command to remove capture: Remove-NetEventSession $sessionName" Write-Host -ForegroundColor Yellow "The data file will be located at $EtlFile." -} \ No newline at end of file +}