# Copyright © 2008, Microsoft Corporation. All rights reserved.
function GetRuntimePath([string]$fileName = $(throw "No file name is specified"))
{
if([string]::IsNullorEmpty($fileName))
{
throw "Invalid file name"
}
[string]$runtimePath = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()
return Join-Path $runtimePath $fileName
}
function RegSnapin([string]$dllName = $(throw "No dll is specified"))
{
$dllPath = ".\" + $dllName
Import-Module $dllPath
}
function UnregSnapin([string]$dllName = $(throw "No dll is specified"))
{
$moduleName = $dllName.TrimEnd(".dll")
Remove-Module $moduleName
}
function GetExistingNDFInstance($IncidentID)
{
&{
#if fails we start a new session
$script:ExpectingException = $true
$ndf = new-object -comObject ndfapi.NetworkDiagnostics.1 -strict
$ndf.OpenExistingIncident($IncidentID); #throws exception if fails
$script:ExpectingException = $false
return $ndf
}
trap [Exception]
{
if($script:ExpectingException)
{
$script:ExpectingException = $false
"Exception: " + $_.Exception.GetType().FullName + " Message: " + $_.Exception.Message | convertto-xml | Update-DiagReport -id OpenExistingSessionFailure -name "Open Existing Session" -description "Failed while opening existing NDF session." -verbosity Debug
return $null;
}
else
{
#rethrow exception
throw $_.Exception;
}
}
}
function WaitWithProgress($ActivityNoDetails, $WaitHandle, $Ndf)
{
$lastProgress = $null
do {
$progress = $Ndf.Progress
if(($progress -ne $null) -and ($progress.Length -gt 0) -and !($progress -eq $lastProgress))
{
Write-DiagProgress -activity $progress
$lastProgress = $progress
}
elseif(($progress.Length -eq 0) -and !($ActivityNoDetails -eq $lastProgress))
{
#clear the last progress string, use alternate description of operation
Write-DiagProgress -activity $ActivityNoDetails
$lastProgress = $ActivityNoDetails
}
&{
$WaitHandle.Wait($ProgressUpdateDelay)
break
}
trap [Exception]
{
#timed out, continue waiting
continue
}
} while($true)
}
# if filtered mode is enabled and the root cause is not on the filter list then return TRUE
function GetRootCauseFilterValue($RootCause, [ref]$FilterMode)
{
#assume filtering is enabled
$filteringEnabled = $true;
if($FilterMode)
{
$FilterMode.value = 0;
}
$regValue = get-itemproperty -path hklm:\SYSTEM\CurrentControlSet\Control\NetDiagFx\Config -name FilterMode -ErrorAction SilentlyContinue -ErrorVariable regError
if($regValue)
{
if($FilterMode)
{
$FilterMode.value = $regValue.FilterMode;
}
$filteringEnabled = !($regValue.FilterMode -eq $FM_DISABLED);
}
elseif($regError)
{
if(!($regError[0].CategoryInfo.Category -eq "InvalidArgument") -and !($regError[0].CategoryInfo.Category -eq "ObjectNotFound"))
{
" Warning: Unexpected error when reading FilterMode key: " + $regError[0].CategoryInfo.Category | convertto-xml | Update-DiagReport -id UnexpectedRegError -name "Unexpected Registry Error" -verbosity Debug
}
}
if(!$filteringEnabled)
{
#allow all root causes
return $FV_NOTFILTERED;
}
else
{
#get the registry value and make the filtering decision
$rootCauseID = $RootCause.RootCauseID;
$regValue = get-itemproperty -path hklm:\SYSTEM\CurrentControlSet\Control\NetDiagFx\Config\RC -name $rootCauseID -ErrorAction SilentlyContinue -ErrorVariable regError
if($regValue)
{
#root cause present, check the entry type
$filterValue = $regValue.$rootCauseID;
if($filterValue -eq 1)
{
#exclusion list, force enabled filter mode so that the RC is dropped
if($FilterMode)
{
$FilterMode.value = $FM_ENABLED;
}
return $FV_FILTERED;
}
else
{
#inclusion list, not filtered
return $FV_NOTFILTERED;
}
}
else
{
if(!($regError[0].CategoryInfo.Category -eq "InvalidArgument") -and !($regError[0].CategoryInfo.Category -eq "ObjectNotFound"))
{
" Warning: Unexpected error when reading Root Cause key : " + $regError[0].CategoryInfo.Category | convertto-xml | Update-DiagReport -id UnexpectedRegError -name "Unexpected Registry Error" -verbosity Debug
}
#could not find root cause
return $FV_MISSING;
}
}
}
function GetCatchAllInformation($RootCauseEnum, [ref]$CatchAlls, [ref]$CatchAllsIndex, [ref]$CatchAllsAltRC)
{
$localCatchAlls = @{}; #will contain the RC's with catch all repairs, indexed by HC it applies to
$localCatchAllsIndex = @{}; #will hold the index of the catch-all in the root cause list
$localCatchAllsAltRC = @{}; #will contain the alternate RC ID for the catch all, if available, indexed by root cause
$rootCauseCount = $RootCauseEnum.Count;
$RootCauseEnum.Reset()
for($i=0; $i -lt $rootCauseCount; $i++)
{
$rootCause=$RootCauseEnum.Next;
$repairEnum = $rootCause.Repairs;
$repairCount = $repairEnum.Count;
$repairEnum.Reset();
for($curRep=0; $curRep -lt $repairCount; $curRep++)
{
$repair = $repairEnum.Next;
if($repair.Flags -band $RF_RESERVED_CA)
{
$altRC = GetExtensionValue ($repair.DescriptionEx) ("CatchAllRC");
#HC's this applies to
$catchAllHCString = GetExtensionValue ($repair.DescriptionEx) ("CatchAllHCs");
if($catchAllHCString)
{
#semi-colon separated
$catchAllHCList = $catchAllHCString.Split(";");
for($curHC=0; $catchAllHCList[$curHC]; $curHC++)
{
#if a RC is filtered for this HC, this catch all will be used
$localCatchAlls[$catchAllHCList[$curHC]]= $rootCause;
if($altRC)
{
#store the alternate RC
$localCatchAllsAltRC[$rootCause] = $altRC;
}
}
#store the index
$localCatchAllsIndex[$rootCause]= $i;
}
break;
}
}
}
#return back the values collected
if($CatchAlls)
{
$CatchAlls.value = $localCatchAlls;
}
if($CatchAllsIndex)
{
$CatchAllsIndex.value = $localCatchAllsIndex;
}
if($CatchAllsAltRC)
{
$CatchAllsAltRC.value = $localCatchAllsAltRC;
}
}
function GetParameters($DescriptionEx, $Params)
{
$toReturn = @{};
#this is a XML based root cause, get replacement parameters
$paramEnum = $DescriptionEx.Parameters;
$paramCount = $paramEnum.Count;
$paramEnum.Reset()
for($curP=0;$curP -lt $paramCount; $curP++)
{
$param = $paramEnum.Next;
if($Params[$param.Name] -eq $null)
{
$toReturn += @{$param.Name = $param.Value}
}
}
return $toReturn;
}
function GetKeywords($DescriptionEx)
{
if($DescriptionEx -eq $nul)
{
return "";
}
$toReturn = "";
#this is a XML based root cause, get replacement parameters
$paramEnum = $DescriptionEx.Extensions;
$paramCount = $paramEnum.Count;
$paramEnum.Reset()
for($curP=0;$curP -lt $paramCount; $curP++)
{
$param = $paramEnum.Next;
if($param.Name -eq "Keyword")
{
if($toReturn.Length -gt 0)
{
$toReturn += "+";
}
$toReturn += """" + $param.Value + """";
}
}
return $toReturn.Replace(" ", "+");
}
function GetExtensionValue($DescriptionEx, $ExtensionName)
{
if($DescriptionEx)
{
$paramEnum = $DescriptionEx.Extensions;
$paramCount = $paramEnum.Count;
$paramEnum.Reset()
for($curP=0;$curP -lt $paramCount; $curP++)
{
$param = $paramEnum.Next;
if($param.Name -eq $ExtensionName)
{
return $param.Value;
}
}
}
return $null;
}
#are we an administrator or LUA?
function IsAdmin()
{
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [System.Security.Principal.WindowsPrincipal]($identity)
return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
#the security boundary safe data now contains two items of information
#(1) the incident ID GUID (without the { } brackets)
#(2) flags: whether the process is LUA (0) or Admin (1)
#the two items are separated by a semicolon
function GetSBSData($IncidentID)
{
$flags = 0
$admin = IsAdmin
if($admin)
{
$flags = 1
}
return $IncidentID.Substring(1,$IncidentID.Length-2) +":"+$flags
}
#the function takes a security boundary safe data and splits it into
#the incident ID GUID (putting back the {} brackets), and the
#flags
function SplitSBSData($SBSData, [ref]$IncidentID, [ref]$Flags)
{
$IncidentID.value = "{" + $SBSData.Substring(0,$SBSData.IndexOf(":")) + "}"
$Flags.value = $SBSData.Substring($SBSData.IndexOf(":")+1)
}
function GetInstanceHashCode($RootCause, $CatchAllInUse)
{
$hashCode = "";
$repairEnum = $RootCause.Repairs;
$repairCount = $repairEnum.Count;
$repairEnum.Reset();
if($repairCount -gt 0)
{
$repair = $repairEnum.Next
if($CatchAllInUse)
{
#make sure we use the hash for the first catch-all repair
for($i=0; ($i -lt $repairEnum.Count) -and (!($repair.Flags -band $RF_RESERVED_CA)); $i++)
{
$repair = $repairEnum.Next;
}
}
$hashCode = $repair.Description.GetHashCode();
}
else
{
$hashCode = $RootCause.Description.GetHashCode();
}
return $hashCode;
}
function GetInstanceIDRC($RootCause, $CatchAlls, $CatchAllsAltRC)
{
$rootCause = $RootCause;
$rootCauseID = $RootCause.RootCauseID;
#if catch all is in use, we need to use the catch all root cause information in the
#instance ID so that all catch-all matches go to the same root cause in the session
$catchAllInUse = $null;
$filterValue = GetRootCauseFilterValue ($RootCause) ($null);
if($filterValue -eq $FV_MISSING)
{
#rc is not in the filter list, catch all instance ID information should be used if present
$className = $rootCause.ClassName;
if($className)
{
#is there a chatch all fo the helper class?
$catchAllRC = $CatchAlls[$className];
if($catchAllRC)
{
#user the catch-all root cause
$rootCause = $catchAllRC;
#does the catch-all have an alternate root cause?
$altRCID = $CatchAllsAltRC[$catchAllRC];
if($altRCID)
{
#use alternate root cause ID
$rootCauseID = $catchAllsAltRC[$catchAllRC];
}
$catchAllInUse = $true;
}
}
}
$hashCode = GetInstanceHashCode($rootCause) ($catchAllInUse);
return $rootCauseID.Substring(1,$rootCauseID.Length-2) + ":" + $hashCode;
}
function GetInstanceID($RootCauseIndex, $RootCauses, $CatchAlls, $CatchAllsAltRC)
{
#approach for generating unique instance ID:
# (1) the root cause GUID (stripped of the { })
# (2) the hash code of the first repair's text
# the repair text is guaranteed to be unique between root causes, and constant across reruns
# If no repairs, then the root cause text hash code is used
$hashCode = 0
$rootCauseID = 0
#find the first repair for this root cause
$rootCauseCount = $RootCauses.Count
$RootCauses.Reset()
for($i=0; $i -lt $rootCauseCount; $i++)
{
$rootCause=$RootCauses.Next;
#root cause index is the index of the root cause in the root cause list
#find the specific root cause
if($i -eq $RootCauseIndex)
{
break;
}
}
if($i -eq $rootCauseCount)
{
throw "Could not find a root cause with index " + $RootCauseIndex;
}
return GetInstanceIDRC($rootCause) ($CatchAlls) ($CatchAllsAltRC);
}
function GetSkipReasonText($Reason)
{
#this is for debug, OK to not localize
if($Reason -eq $NDF_SKIPREASON_ADAPTER)
{
return "An interactive fix for another network interface has been attempted."
}
elseif($Reason -eq $NDF_SKIPREASON_DUPLICATE)
{
return "Another repair with the same ID applying to the same network interface has been attempted."
}
}
function InContextEntry()
{
$IT_HelperClassName = Get-DiagInput -ID "IT_HelperClassName"
if($IT_HelperClassName -eq $null -or $IT_HelperClassName[0].Length -eq 0) {
#Failed retriving HelperClassName from In-Context answer file
return $null
}
#get input attributes
$IT_HelperAttributes = Get-DiagInput -ID "IT_HelperAttributes"
if($IT_HelperAttributes -eq $null) {
#Failed retriving HelperAttributes from In-Context answer file
return $null
}
return @{"HelperClassName" = $IT_HelperClassName; "HelperAttributes" = $IT_HelperAttributes}
}
function EscapeForRTF($Text)
{
$textToEscape = $Text.Replace("\","\\")
$sb = New-Object System.Text.StringBuilder $textToEscape.Length;
for($i=0; $i -lt $textToEscape.Length; $i++)
{
$curChar = $textToEscape[$i];
if($curChar -eq '\n')
{
$null = $sb.Append("\par");
}
elseif(($curChar -lt 0x20) -or ($curChar -eq '{') -or ($curChar -eq '}') -or ($curChar -eq '\\'))
{
$null = $sb.Append("\'");
$null = $sb.Append(([int]$curChar).ToString("X2", [System.Globalization.CultureInfo]::InvariantCulture));
}
elseif($curChar -lt 0x80)
{
$null = $sb.Append($curChar);
}
else
{
$null = $sb.Append("\u");
$null = $sb.Append(([int]$curChar).ToString([System.Globalization.CultureInfo]::InvariantCulture));
$null = $sb.Append('?');
}
}
return $sb.ToString();
}
function IsValidURL($URL)
{
&{
$uri = [System.URI]($URL);
$scheme = $uri.scheme;
if(($scheme -eq "http" ) -or ($scheme -eq "https") -or ($scheme -eq "ftp"))
{
return $uri.ToString();
}
else
{
return $null;
}
}
trap [Exception]
{
return $null;
}
}
function GetDefaultBrowser()
{
[string]$assocString = $null
$dll = "NetworkDiagnosticSnapIn.dll"
try
{
RegSnapin $dll
$assocString = [Microsoft.Windows.Diagnosis.Network.AssociationInfo]::GetAssociation("http","open");
trap [Exception]
{
$assocString = $null;
}
}
finally
{
UnregSnapin $dll
}
return $assocString;
}
function GetWebNDFIncidentData($URL, $DefaultConnectivity)
{
#build entry point parameters
$haXML = "URLAT_STRING"
if($DefaultConnectivity)
{
#sqm explorer as the client rather than sdiaghost.exe
$haXML += "NDFSQMCallerApplicationAT_STRINGWindows\Explorer.EXE"
$defaultBrowser = GetDefaultBrowser;
if($defaultBrowser)
{
$haXML += "AppIDAT_STRING"+ $defaultBrowser + ""
}
}
$haXML += ""
return @{"HelperClassName" = "WinInetHelperClass"; "HelperAttributes" =$haXML}
}
function GetValidURL($CandidateURL)
{
$toReturn = $null
$url = IsValidURL $CandidateURL
if($url -eq $null)
{
if($CandidateURL.IndexOf("://") -eq -1)
{
$updatedURL = "http://" + $CandidateURL
$url = IsValidURL $updatedURL
if($url)
{
$toReturn = $url
}
}
}
else
{
$toReturn = $url
}
return $toReturn
}
function GetErrorRTF($Description, $Error)
{
$escapedDesc = EscapeForRTF $Description;
$escapedError = EscapeForRTF $Error;
$rtf = LoadResourceString($ERROR_MSG_RTF_RESOURCE);
return $rtf.Replace("%DESC%", $escapedDesc).Replace("%ERROR%", $escapedError);
}
function WebEntry()
{
$IT_WebChoice = Get-DiagInput -ID "IT_WebChoice"
if($IT_WebChoice -eq $null)
{
#Failed retriving Web Choice
return $null
}
$IT_URL = $DefaultDiagURL
if(!($IT_WebChoice -eq "Internet"))
{
$IT_URL = Get-DiagInput -ID "IT_URL"
if($IT_URL -eq $null) {
#Failed retriving URL
return $null
}
#verify that it is a valid URL
$validURL = GetValidURL $IT_URL[0]
while($validURL -eq $null)
{
#build the RTF text
$replacedError = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.interaction_InvalidURL_FormatError, $IT_URL[0]);
$RTFText = GetErrorRTF ($localizationString.interaction_InvalidURL_Desc) ($replacedError);
#reprompt for input
$IT_URL = Get-DiagInput -ID "IT_Invalid_URL" -p @{"URL" = $IT_URL; "RTFText" = $RTFText}
if($IT_URL -eq $null) {
#Failed retriving URL
return $null
}
$validURL = GetValidURL $IT_URL[0]
}
}
return GetWebNDFIncidentData $validURL $false
}
function IsUNCFormat($UNC)
{
&{
$uri = [System.URI]($UNC);
$scheme = $uri.scheme;
if(($scheme -eq "file" ))
{
if($uri.IsUnc)
{
return $uri.LocalPath;
}
}
return $null;
}
trap [Exception]
{
return $null;
}
}
#function assumes passed in UNC is in \\host\share form (share can be missing)
function ContainsInvalidUNCChars($UNC)
{
&{
#will return an exception if the string has invalid characters
$ignoreResult = [System.IO.Path]::IsPathRooted($UNC)
#check the path for invalid characters
#remove the starting slashes
$tmp = $UNC.Substring(2)
$nextSlash = $tmp.IndexOf("\")
if(($nextSlash -lt 0) -or ($nextSlash -eq ($nextSlash.Length - 1)))
{
#string only contains hostname
#hostname is already validated in IsUNCFormat function
return $false
}
#remove host and backslash after host
$UNCPath = $tmp.Substring($nextSlash+1)
#under certain circumstances some of these make it through the above check
#so we do a direct sanity check here
if(!($UNCPath.IndexOfAny(@('/',':','*','?','"','<','>','|')) -eq -1))
{
return $true;
}
return $false;
}
trap [Exception]
{
return $true;
}
}
function GetValidUNC($CandidateUNC)
{
$toReturn = $null
#is it valid
$unc = IsUNCFormat $CandidateUNC
if($unc)
{
$invalidChars = ContainsInvalidUNCChars $unc
if($invalidChars)
{
$toReturn = -1;
}
else
{
$toReturn = $unc
}
}
return $toReturn;
}
function GetUNCNDFIncidentData($UNC)
{
#build entry point parameters
$haXML = "UNCPathAT_STRING"
return @{"HelperClassName" = "SMBHelperClass"; "HelperAttributes" =$haXML}
}
function FileSharingEntry()
{
$IT_UNC = Get-DiagInput -ID "IT_UNC"
if($IT_UNC -eq $null) {
#Failed retriving UNC path
return $null
}
#assign input to non-array variable to facilitate usage and transform
$validUNC = GetValidUNC $IT_UNC[0]
while((!$validUNC) -or ($validUNC -eq -1))
{
#build the RTF text
#use original entry for re-prompt even though "file://" UNC may have been transformed
$replacedError = "";
if(!$validUNC)
{
$replacedError = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.interaction_InvalidUNC_FormatError, $IT_UNC[0]);
}
else
{
$replacedError = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.interaction_InvalidUNC_CharError, $IT_UNC[0]);
}
$RTFText = GetErrorRTF ($localizationString.interaction_InvalidUNC_Desc) ($replacedError);
#reprompt for input
$IT_UNC = Get-DiagInput -ID "IT_Invalid_UNC" -p @{"UNC" = $IT_UNC; "RTFText" = $RTFText}
if($IT_UNC -eq $null) {
#Failed retriving UNC path
return $null
}
$validUNC = GetValidUNC $IT_UNC[0]
}
return GetUNCNDFIncidentData $validUNC
}
function NetworkAdapterEntry()
{
#enumerate interfaces to build options list
$interfaces = get-wmiobject -class Win32_NetworkAdapter
#hash table with options
$optionList = @()
foreach($curInterface in $interfaces)
{
if($curInterface.GUID -ne $null)
{
$curHash = @{"Name"=$curInterface.NetConnectionID}
$curHash += @{"Description"=$curInterface.NetConnectionID}
$curHash += @{"Value"=$curInterface.GUID}
$optionList += @($curHash)
}
}
if($optionList.Count -gt 1)
{
#add zero guid entry to check all interfaces
$optionList += @(@{"Name"=$localizationString.interaction_AllAdapters; "Description"=$localizationString.interaction_AllAdapters; "Value"="{00000000-0000-0000-0000-000000000000}"; "ExtensionPoint"=""})
#get interface selection from user
$IT_NetworkAdapter = Get-DiagInput -ID "IT_NetworkAdapter" -c $optionList
if($IT_NetworkAdapter -eq $null) {
throw "Failed retriving Network Connetion ID from user"
}
}
elseif($optionList.Count -eq 1)
{
$IT_NetworkAdapter = $optionList[0]["Value"]
}
else
{
#No NICs, do zero GUID diag
$IT_NetworkAdapter = "{00000000-0000-0000-0000-000000000000}"
}
#build entry point parameters
$haXML = "guidAT_GUID" + $IT_NetworkAdapter + ""
return @{"HelperClassName" = "NetConnection"; "HelperAttributes" =$haXML}
}
function WinsockEntry()
{
$IT_RemoteAddress = Get-DiagInput -ID "IT_RemoteAddress"
if($IT_RemoteAddress -eq $null -or $IT_RemoteAddress[0].Length -eq 0) {
#Failed retriving Remote Address
return $null
}
$IT_Protocol = Get-DiagInput -ID "IT_Protocol"
if($IT_Protocol -eq $null -or $IT_Protocol[0].Length -eq 0) {
#Failed retriving Remote Port
return $null
}
$IT_ApplicationID = Get-DiagInput -ID "IT_ApplicationID"
if($IT_ApplicationID -eq $null -or $IT_ApplicationID[0].Length -eq 0) {
#Failed retriving Application ID
return $null
}
#build entry point parameters
$haXML = "remoteaddrAT_SOCKADDR" + $IT_RemoteAddress + "";
$haXML += "protocolAT_UINT32" + $IT_Protocol + "";
$haXML += "localaddrAT_SOCKADDR0.0.0.0";
$haXML += "appidAT_STRING" + $IT_ApplicationID + "";
$haXML += "";
return @{"HelperClassName" = "Winsock"; "HelperAttributes" =$haXML}
}
function GroupingEntry()
{
$IT_GroupName = Get-DiagInput -ID "IT_GroupName"
if($IT_GroupName -eq $null -or $IT_GroupName[0].Length -eq 0) {
#Failed retriving Remote Address
return $null
}
#build entry point parameters
$haXML = "groupnameAT_STRING" + $IT_GroupName + ""
return @{"HelperClassName" = "GroupingHelperClass"; "HelperAttributes" =$haXML}
}
function GetValidExePath($File)
{
&{
$uri = [System.URI]($File);
$scheme = $uri.scheme;
if(($scheme -eq "file" ))
{
#make sure it send in .exe
if($File.ToLower().IndexOf(".exe") -eq ($File.Length - 4))
{
return $File;
}
}
return $null;
}
trap [Exception]
{
return $null;
}
}
function InboundEntry()
{
$staticOptionRes = @($INBOUND_FILESHARE_RESOURCE, $INBOUND_REMOTEDESKTOP_RESOURCE, $INBOUND_DISCOVERY_RESOURCE)
$staticOptions = @($INBOUND_FILESHARE_PARAM, $INBOUND_REMOTEDESKTOP_PARAM, $INBOUND_DISCOVERY_PARAM)
# If defined for the corresponding option, the item will be filtered out if the current sku matches anything in the list
# Sku values as defined in the OperatingSystemSKU property of Win32_OperatingSystem
$SKUFilters = @($null, @(2,3,5,11), $null)
#get the SKU, to filter out inappropriate static options
$SKUObject = get-wmiobject -class Win32_OperatingSystem -property "OperatingSystemSKU"
$SKU = $SKUObject.OperatingSystemSKU
$optionList = @()
$curOptionIndex = 0
for($curStaticOption = 0; $curStaticOption -lt $staticOptions.Length; $curStaticOption++)
{
$SKUFilter = $SKUFilters[$curStaticOption]
if($SKUFilter)
{
if($SKUFilter -contains $SKU)
{
#should filter out this option from the list because it is not present in the SKU
continue;
}
}
$curApp = LoadResourceString($staticOptionRes[$curStaticOption])
$curHash = @{}
$curHash.Add("Name",$curApp)
$curHash.Add("Value",$curOptionIndex)
$curHash.Add("Description",$curApp)
$curHash.Add("HelperAttributeName","serviceid")
$curHash.Add("HelperAttributeValue",$staticOptions[$curStaticOption])
$optionList += $curHash
$curOptionIndex++
}
#add dynamic options (do not fail if call fails)
$script:ExpectingException = $true
$dll = "NetworkDiagnosticSnapIn.dll"
try
{
RegSnapin $dll
$droppedApps = [Microsoft.Windows.Diagnosis.Network.FirewallApi.ManagedMethods]::GetDiagnosticAppInfo()
$script:ExpectingException = $false
if($droppedApps)
{
foreach($droppedApp in $droppedApps)
{
#omit svchosts since we cannot display a friendly name for them
if($droppedApp.Path.IndexOf("svchost") -eq -1)
{
$appEntryDisplayStr = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.interaction_Inbound_Exe, $droppedApp.FriendlyName);
$curHash = @{}
$curHash.Add("Name",$appEntryDisplayStr)
$curHash.Add("Value",$curOptionIndex)
$curHash.Add("Description",$droppedApp.FriendlyName)
$curHash.Add("HelperAttributeName","appid")
$curHash.Add("HelperAttributeValue",$droppedApp.Path)
$optionList += $curHash
$curOptionIndex++
}
}
}
}
finally
{
UnregSnapin $dll
}
#add the last option to browse for files
$curApp = LoadResourceString($INBOUND_OTHER_RESOURCE)
$curHash = @{}
$curHash.Add("Name",$curApp)
$curHash.Add("Value",$curOptionIndex)
$curHash.Add("Description",$curApp)
$curHash.Add("HelperAttributeName","serviceid")
$curHash.Add("HelperAttributeValue",$INBOUND_OTHER_RESOURCE)
$optionList += $curHash
#trap exception if it happens, and if expected don't fail
trap [Exception]
{
if($script:ExpectingException)
{
$script:ExpectingException = $false
"Exception: " + $_.Exception.GetType().FullName + " Message: " + $_.Exception.Message | convertto-xml | Update-DiagReport -id GetDiagAppInfoFailure -name "GetDiagAppInfo" -verbosity Debug
continue;
}
else
{
#rethrow exception
throw $_.Exception;
}
}
$IT_ServiceName = Get-DiagInput -ID "IT_ServiceName" -c $optionList
if($IT_ServiceName -eq $null -or $IT_ServiceName[0].Length -eq 0) {
#Failed retriving service name
return $null
}
$optionSelected = $optionList[$IT_ServiceName]
$optionSelected = $optionSelected[0] #need to to this to get access to the dictionary entry object
$HelperAttributeName = $null
$HelperAttributeValue = $null
if($optionSelected.HelperAttributeValue -eq $INBOUND_OTHER_RESOURCE)
{
#show the file browsing interaction so that the user can pick their own executable
$IT_BrowseFile = Get-DiagInput -ID "IT_BrowseFile"
if($IT_BrowseFile -eq $null) {
#Failed retriving file
return $null
}
$validExe = GetValidExePath $IT_BrowseFile[0]
while(!$validExe)
{
#build the RTF text
#build the error
$replacedError = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.interaction_InvalidExe_FormatError , $IT_BrowseFile[0]);
#only a single line
$RTFText = GetErrorRTF ($localizationString.interaction_InvalidExe_Desc) ($replacedError);
#reprompt for input
$IT_BrowseFile = Get-DiagInput -ID "IT_Invalid_BrowseFile" -p @{"File" = $IT_BrowseFile[0]; "RTFText" = $RTFText}
if($IT_BrowseFile -eq $null) {
#Failed retriving file
return $null
}
$validExe = GetValidExePath $IT_BrowseFile[0]
}
$HelperAttributeName = "appid"
$HelperAttributeValue = $IT_BrowseFile;
}
else
{
$HelperAttributeName = $optionSelected.HelperAttributeName
$HelperAttributeValue = $optionSelected.HelperAttributeValue
}
#build entry point parameters
$haXML = ""
$haXML += "" + $HelperAttributeName + "AT_STRING" + $HelperAttributeValue + ""
$haXML += "localaddrAT_SOCKADDR0.0.0.0"
$haXML += ""
return @{"HelperClassName" = "Winsock"; "HelperAttributes" =$haXML}
}
function DirectAccessEntry()
{
$toReturn = $null;
$path = "hklm:SOFTWARE\Policies\Microsoft\Windows\NetworkConnectivityStatusIndicator\CorporateConnectivity";
if(test-path $path)
{
$corpReg = get-itemproperty -path $path
if($corpReg.WebProbeURL -and $corpReg.WebProbeURL.Length -gt 0)
{
#build entry point parameters
$haXML = "URLAT_STRING" + $corpReg.WebProbeURL + ""
$toReturn = @{"HelperClassName" = "WinInetHelperClass"; "HelperAttributes" =$haXML}
}
elseif($corpReg.DnsProbeHost -and $corpReg.DnsProbeHost -gt 0)
{
#build entry point parameters
$haXML = "QueryNameAT_STRING" + $corpReg.DnsProbeHost + ""
$toReturn = @{"HelperClassName" = "DnsHelperClass"; "HelperAttributes" =$haXML}
}
}
return $toReturn;
}
function DefaultConnectivityFollowUpEntry()
{
$toReturn = $null
$IT_DefaultConnectivityInitialChoice = Get-DiagInput -ID "IT_DefaultConnectivityInitialChoice"
if($IT_DefaultConnectivityInitialChoice -eq $null -or $IT_DefaultConnectivityInitialChoice[0].Length -eq 0)
{
#Failed retriving service name
return $null
}
#clear the progress so that the last step doesn't show before things get started again
Write-DiagProgress -activity " "
if($IT_DefaultConnectivityInitialChoice -eq "Other")
{
#query which other entry point they wish to use
$IT_DefaultConnectivityOtherChoice = Get-DiagInput -ID "IT_DefaultConnectivityOtherChoice"
if($IT_DefaultConnectivityOtherChoice[0].Length -eq 0)
{
#Failed retriving service name
return $null
}
if($IT_DefaultConnectivityOtherChoice -eq "Inbound")
{
$toReturn = InboundEntry
}
elseif($IT_DefaultConnectivityOtherChoice -eq "DirectAccess")
{
$toReturn = DirectAccessEntry
if(!$toReturn)
{
#not provisioned, root cause outside of NDF
Update-DiagRootCause -ID "{E42E5B5A-16E0-43f1-AB32-C94C608D269D}" -Detected $true
return;
}
}
elseif($IT_DefaultConnectivityOtherChoice -eq "NetworkAdapter")
{
$toReturn = NetworkAdapterEntry
}
}
else
{
#query for the URL/UNC path
$IT_URLOrUNC = Get-DiagInput -ID "IT_URLOrUNC"
$validUNC = $null
#Is it a valid URL?
$validURL = GetValidURL $IT_URLOrUNC[0]
if(!$validURL)
{
$validUNC = GetValidUNC $IT_URLOrUNC[0]
}
while((!$validURL) -and
((!$validUNC) -or ($validUNC -eq -1)))
{
#build the RTF text
$replacedError = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.interaction_InvalidURLOrUNC_FormatError, $IT_URLOrUNC[0]);
$RTFText = GetErrorRTF ($localizationString.interaction_InvalidURLOrUNC_Desc) ($replacedError);
#reprompt for input
$IT_URLOrUNC = Get-DiagInput -ID "IT_Invalid_URLOrUNC" -p @{"URLOrUNC" = $IT_URLOrUNC; "RTFText" = $RTFText}
if($IT_URLOrUNC -eq $null) {
#Failed retriving URL/UNC path
return $null
}
$validURL = GetValidURL $IT_URLOrUNC[0]
if(!$validURL)
{
$validUNC = GetValidUNC $IT_URLOrUNC[0]
}
}
if($validURL)
{
$toReturn = GetWebNDFIncidentData $validURL $false
}
else
{
$toReturn = GetUNCNDFIncidentData $validUNC
}
}
return $toReturn
}
function GetRepair($RepairRank, $RCEnum)
{
$RCEnum.Reset()
$rootCauseCount = $RCEnum.Count
for($i=0; $i -lt $rootCauseCount; $i++)
{
$rootCause = $RCEnum.Next;
$repairEnum = $rootCause.Repairs
$repairCount = $repairEnum.Count;
for($r=0; $r -lt $repairCount; $r++)
{
$curRep = $repairEnum.Next
if($curRep.Rank -eq $RepairRank)
{
return $curRep
}
}
}
return $null
}
function SplitString($FullString, [ref]$Title, [ref]$Rest)
{
$newLineIndex = $FullString.IndexOf("`n");
if(!($newLineIndex -eq -1))
{
$Title.value = $FullString.Substring(0, $newLineIndex)
$Rest.value = $FullString.Substring($newLineIndex+1)
}
else
{
$Title.value = $FullString
$Rest.value = ""
}
}
function GetTitleAndDesc($RCorRep, [ref]$Title, [ref]$Desc)
{
$descEx = $RCorRep.DescriptionEx
if(!($descEx -eq $null))
{
$Title.value = $descEx.Title
$Desc.value = $descEx.Description
if($Desc.value -eq $null)
{
$Desc.value = ""
}
}
else
{
#split the string into title and description using \n
SplitString $RCorRep.Description ($Title) ($Desc)
}
}
function GetButtonParams($Repair, [ref]$Params, $DefaultName, $DefaultDesc, $ExtensionName, $ButtonITName, $ButtonITDesc)
{
#defaults
$buttonName = $DefaultName;
$buttonDesc = $DefaultDesc;
$descriptionEx = $Repair.DescriptionEx;
if($descriptionEx)
{
#this is a XML based root cause, get replacement parameters
$paramEnum = $DescriptionEx.Extensions;
$paramCount = $paramEnum.Count;
$paramEnum.Reset()
for($curP=0;$curP -lt $paramCount; $curP++)
{
$param = $paramEnum.Next;
if($param.Name -eq $ExtensionName)
{
SplitString $param.Value ([ref]$buttonName) ([ref]$buttonDesc);
break;
}
}
}
$Params.value.Add($ButtonITName, $buttonName);
$Params.value.Add($ButtonITDesc, $buttonDesc);
}
function GetContinueButtonParams($Repair, [ref]$Params)
{
GetButtonParams $Repair $Params $localizationString.interaction_DefaultContinueButtonName $localizationString.interaction_DefaultContinueButtonDescription "ContinueButtonText" "IT_P_ContinueButtonName" "IT_P_ContinueButtonDescription"
}
function GetLaunchButtonParams($Repair, [ref]$Params)
{
GetButtonParams $Repair $Params $localizationString.interaction_DefaultLaunchButtonName "" "LaunchButtonText" "IT_P_LaunchButtonName" "IT_P_LaunchButtonDescription"
}
function GetUnmanifestedRootCause($RootCause, [ref]$Params, $catchAllRC)
{
#Get name and description of root cause
$Name = "";
$Desc = "";
#don't use DescriptionEx for unmanifested root causes
SplitString $RootCause.Description ([ref]$Name) ([ref]$Desc)
#add to outgoing param list
$Params.value.Add("RootCauseName", $Name);
$Params.value.Add("RootCauseDescription", $Desc);
#check whether repairs require elevation of not
$LUARepairs = @();
$elevateRepairs = @();
$localRepairs = @();
$repairEnum = $rootCause.Repairs
$repairEnum.Reset()
for($i=0; $i -lt $repairEnum.Count; $i++)
{
$repair = $repairEnum.Next
if($catchAllRC -and !($repair.Flags -band $RF_RESERVED_CA))
{
#this is a catch all RC treatment, so we should skip the non-catch all repairs
continue;
}
elseif(!$catchAllRC -and ($repair.Flags -band $RF_RESERVED_CA))
{
#this is not a catch all RC treatment, so we should skip catch all repairs
continue;
}
$requiredSid = $Repair.RequiredSidType;
if($requiredSid -eq $null)
{
throw "Could not retrieve required repair SID"
}
elseif(($requiredSid -eq $WinBuiltinAdministratorsSid) -or ($requiredSid -eq $WinBuiltinNetworkConfigurationOperatorsSid))
{
$elevateRepairs += @($repair);
}
elseif($requiredSid -eq $WinLocalLogonSid)
{
$localRepairs += @($repair);
}
else
{
$LUARepairs += @($repair);
}
}
$LUACount = $LUARepairs.Length;
$elevateCount = $elevateRepairs.Length;
$localCount = $localRepairs.Length;
if($elevateCount + $LUACount -gt 2)
{
#currently we don't support more than two repairs for
#unmanifested repairs.
"LUA: " + $LUACount + " Elevation: " + $elevateCount | convertto-xml | Update-DiagReport -id UnmanifestedRootCause -name "Unmanifested Root Cause" -description "Unsupported number of total repairs for unmanifested root cause." -verbosity Debug
return $null;
}
elseif($localCount -and ($elevateCount -or $LUACount))
{
#we don't support a combination of Local repairs with any other kind of repair
"LUA: " + $LUACount + " Elevation: " + $elevateCount | convertto-xml | Update-DiagReport -id UnmanifestedRootCause -name "Unmanifested Root Cause" -description "Unsupported repair combination including Local repair." -verbosity Debug
return $null;
}
elseif($localCount -gt 1)
{
#we don't support more than one Local repair
"Local: " + $localCount | convertto-xml | Update-DiagReport -id UnmanifestedRootCause -name "Unmanifested Root Cause" -description "Unsupported number of total Local repairs." -verbosity Debug
return $null;
}
#populate the parameter data.
#NOTE: LUA repairs always are listed before
# elevation repairs (makes manifesting easier)
$repairCount = $null;
#guarantees that LUA repairs are placed before elevated
$allRepairs = $LUARepairs + $elevateRepairs + $localRepairs;
#lua repairs
for($i=0; $i -lt $allRepairs.Length; $i++, $repairCount++)
{
$repair = $allRepairs[$i];
#get name and description of repair
#don't use DescriptionEx for unmanifested root causes
SplitString $repair.Description ([ref]$Name) ([ref]$Desc)
#add to outgoing param list
$Params.value.Add("RepairName"+$repairCount, $Name);
$Params.value.Add("RepairDescription"+$repairCount, $Desc);
$Params.value.Add("RepairID"+$repairCount, $repair.RepairID);
}
#add security boundary safe data
$data = GetSBSData($Global:ndf.IncidentID)
$params.value.Add("SBSData", $data)
#keywords for escalation
$keywords = GetKeywords($RootCause.DescriptionEx);
if($keywords.Length -gt 0)
{
$params.value.Add("Keywords", $keywords);
}
if(($LUACount -eq 1) -and ($elevateCount -eq 1))
{
#return placeholder RC with mix of admin and LUA repairs
return "{000000000-0000-0000-0000-000000000005}"
}
elseif($elevateCount -eq 1)
{
#return placeholder RC with single elevation repair
return "{000000000-0000-0000-0000-000000000002}"
}
elseif($elevateCount -eq 2)
{
#return placeholder RC with two Admin repairs
return "{000000000-0000-0000-0000-000000000004}"
}
elseif($LUACount -eq 1)
{
#check whether the single repair is info only or help topic to use alternate root cause
$repair = $LUARepairs[0];
$uiInfo = $repair.UiInfo;
$repairFlags = $repair.Flags;
if(($uiInfo -and ($uiInfo.Type -eq $UIT_HELP_PANE)) -or ($repairFlags -band $RF_INFORMATION_ONLY))
{
#return placeholder RC with single info only or help topic repair
return "{000000000-0000-0000-0000-000000000006}"
}
else
{
#return placeholder RC with single LUA repair
return "{000000000-0000-0000-0000-000000000001}"
}
}
elseif($LUACount -eq 2)
{
#return placeholder RC with two LUA repairs
return "{000000000-0000-0000-0000-000000000003}"
}
elseif($localCount -eq 1)
{
#return placeholder RC with a single Local user repair
return "{000000000-0000-0000-0000-000000000007}"
}
return $null;
}
function CreateCab($FileList, $TargetFolder, $TargetCAB)
{
$ddf = @"
.OPTION EXPLICIT
.Set CabinetNameTemplate=$TargetCAB
.set DiskDirectoryTemplate=CDROM
.Set CompressionType=MSZIP
.Set UniqueFiles=`"OFF`"
.Set Cabinet=on
.Set DiskDirectory1=$TargetFolder
$($OFS="`r`n"; $FileList)
"@;
$ddfFile = "NetworkConfiguration.ddf";
$ddf | Out-File $ddfFile -Encoding Ascii;
makecab /f $ddfFile;
$succeeded = $?;
del $ddfFile;
return $succeeded;
}
function HereString($text)
{
$here = "@'`n" + $text + "`n'@"
return $here
}
#$Commands is an array of hash pairs "command": the command line to run, "file": the target filename,
#appended to the end of the command line
function AddCommandOutputToSession($Commands, $TargetCABName, $ReportHeader)
{
#lets create the temporary folder for all this data
$tempFolder = [System.IO.Path]::GetTempFileName();
del $tempFolder; #delete the file
mkdir $tempFolder; #make it into a folder
#go into the folder to avoid leaving side-effect files behind
pushd $tempFolder;
#run the commands in the list
$fileList = @();
$timeMeasure = @(); #keeps track of length of execution for commands
foreach($item in $Commands)
{
#measure time it takes to execute commands
$start = get-date
$targetFile = $tempFolder + "\" + $item["file"];
$cmdline = $item["command"] + " " + (HereString $targetFile);
$err = $($out = Invoke-expression $cmdline) 2>&1;
if(!$err)
{
#the call succeeded, add the target file to the list to CAB
$fileList += @($item["file"]);
}
$runtime = (get-date) - $start;
$timeMeasure += @(@{"command"=$item["command"];"runtime (ms)"=$runtime.TotalMilliseconds});
}
#create a CAB of the files
$start = get-date
if(CreateCab ($fileList) (".\") ($TargetCABName))
{
$runtime = (get-date) - $start;
$timeMeasure += @(@{"command"="makecab.exe";"runtime (ms)"=$runtime.TotalMilliseconds});
Update-DiagReport -ID NetworkData -name $ReportHeader -File ($tempFolder + "\"+ $TargetCABName)
}
$timeMeasure | convertto-xml | Update-DiagReport -ID ConfigCollectionRuntime -Name "Data Collection Time" -Verbosity Debug
popd;
remove-item -recurse -force $tempFolder;
}
function AddNetworkInfoToSession()
{
Write-DiagProgress -activity $localizationString.progress_Collecting_Config
$commands = @(
@{"command"="ipconfig /all >"; "file"="ipconfig.all.txt"};
@{"command"="route print >"; "file"="route.print.txt"};
);
AddCommandOutputToSession ($commands) ("NetworkConfiguration.cab") ($localizationString.OtherNetworkConfigReportName);
Write-DiagProgress -activity " "
}
function GetUniqueFileName($IncidentID, $Operation)
{
#get temp folder location
$tempFolder = get-childitem -path env:temp
#strip { } at the ends of the incident ID GUID, generate file name
$uniqueFileName = $tempFolder.Value+"\"+$IncidentID.Substring(1,$IncidentID.Length-2)+"." + $Operation
#append whether it's admin or not (to avoid name clashes, as op count resets after elevation)
$isAdmin = IsAdmin
if($isAdmin)
{
$uniqueFileName += ".Admin";
}
#initialize or increment op count
if($Global:OpCount -eq $null)
{
$Global:OpCount = 0
}
else
{
$Global:OpCount++;
}
$uniqueFileName += "." + $Global:OpCount + ".etl";
return $uniqueFileName
}
function AddTraceFileToSession($Ndf, $Name, $Operation)
{
#NDF flushes the trace file every time we query for it
#A unique name needs to be generated each time we add the file to the report,
#otherwise it will overwrite the existing ETL file
#The naming convention is as follows:
# IncidentID.Operation([Admin]).Counter.etl
Write-DiagProgress -activity $localizationString.progress_Collecting_Logs
$traceFile = $Ndf.TraceFile
if($traceFile)
{
#get unique name
$newFileName = GetUniqueFileName $Ndf.IncidentID $Operation
#rename file
move "$traceFile" "$newFileName"
#add it to the report
Update-DiagReport -ID NDFDebugLog -name $Name -File $newFileName
#add HC events to the report
AddNewHCEventsToSession $newFileName
#delete the file name
del "$newFileName"
}
else
{
$Name | convertto-xml | Update-DiagReport -id TraceFile -name "Trace File" -description "Failed while trying to retrieve the trace file for the session." -verbosity Debug
}
Write-DiagProgress -activity " "
}
function AddNewHCEventsToSession($TraceFile)
{
#we keep track of all the events added to the report, so we don't add duplicates (this function is called multiple times)
if($Global:ReportEvents -eq $null)
{
$Global:ReportEvents = @{};
}
$script:ExpectingException = $false
&{
$script:ExpectingException = $true
$events = get-winevent -path $TraceFile -Oldest -FilterXPath "*[System[Provider[@Name='Microsoft-Windows-Diagnostics-Networking'] and (EventID=6100)]]" -ErrorAction SilentlyContinue
$script:ExpectingException = $false
foreach($event in $events)
{
#events indexed by time they were emitted
if(($event -ne $null) -and !$Global:ReportEvents.ContainsKey($event.TimeCreated))
{
#Add helper class name to title so that it's easily distinguishable in the report without having to expand it
$eventTitle = [System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $localizationString.HelperClassEventNameWithHCName,
[System.Globalization.CultureInfo]::CurrentUICulture.TextInfo.ToTitleCase($event.Properties[0].Value));
"" | Update-DiagReport -id DiagInformation -name $eventTitle
$Global:ReportEvents.Add($event.TimeCreated, $event)
}
}
}
trap [Exception]
{
if($script:ExpectingException)
{
"No admin helper class events were found." | convertto-xml | Update-DiagReport -id DiagEvents -name "Helper Class Events" -verbosity Debug
}
else
{
"Exception: " + $_.Exception.GetType().FullName + " Message: " + $_.Exception.Message | convertto-xml | Update-DiagReport -id DiagEventsFailure -name "Helper Class Events" -description "Failed while retrieving helper class events." -verbosity Debug
}
return
}
}
function LoadResourceString($ResourceString)
{
[string]$bufStr = $null
$dll = "NetworkDiagnosticSnapIn.dll"
try
{
RegSnapin $dll
$bufferSize = 512
$buffer = New-Object System.Text.StringBuilder $bufferSize
[Microsoft.Windows.Diagnosis.Network.NativeShellMethods]::SHLoadIndirectString($ResourceString, $buffer, $bufferSize, [IntPtr]::Zero)
$bufStr = $buffer.ToString()
}
finally
{
UnregSnapin $dll
}
return $bufStr
}
function IsDPSStarted()
{
$dpsService = get-service "DPS"
if($dpsService)
{
if($dpsService.Status -ne "Running")
{
return $false;
}
}
return $true;
}
function IsDPSDisabled()
{
$dpsService = gwmi win32_service -f "name='DPS'"
if($dpsService)
{
if($dpsService.StartMode -eq "Disabled")
{
return $true;
}
}
return $false;
}
function IsSafeMode()
{
[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
return [System.Windows.Forms.SystemInformation]::BootMode -ne 0
}
function IsHelpTopicAllowed($Link)
{
$regValue = get-itemproperty -path hklm:\SYSTEM\CurrentControlSet\Control\NetDiagFx\Config\HelpTopic -name $Link -ErrorAction SilentlyContinue -ErrorVariable regError
if($regValue)
{
# check the DWORD value (the key to the value is the Value name: i.e., $Link)
# 1 - enabled
# otherwise - disabled
$filterValue = $regValue.$Link;
if($filterValue -eq 1)
{
return $true;
}
else
{
return $false;
}
}
elseif ($regError)
{
if(!($regError[0].CategoryInfo.Category -eq "InvalidArgument") -and !($regError[0].CategoryInfo.Category -eq "ObjectNotFound"))
{
" Warning: Unexpected error when reading Help Topic Cause key : " + $regError[0].CategoryInfo.Category | convertto-xml | Update-DiagReport -id UnexpectedRegError -name "Unexpected Registry Error" -verbosity Debug
}
return $false;
}
}