.log | WID error logs (only collected if WID Deployments and if the cummulative size of the files is <=10mb) |
196 |
--------------------------------------------------------------------------------
/ADFS-tracing.ps1:
--------------------------------------------------------------------------------
1 | ############################################################################################################
2 | # ADFS troubleshooting - Data Collection
3 | # Supported OS versions: Windows Server 2012 to Server 2025
4 | # Supported role: ADFS on 2012 to 2022, ADFS proxy server (2012) and Web Application Proxy (2012 R2 to 2022)
5 | ############################################################################################################
6 |
7 | param (
8 | [Parameter(Mandatory=$false)]
9 | [string] $Path,
10 |
11 | [Parameter(Mandatory=$false)]
12 | [switch]$Tracing,
13 |
14 | [Parameter(Mandatory=$false)]
15 | [switch]$NetworkTracing,
16 |
17 | [Parameter(Mandatory=$false)]
18 | [switch]$LDAPTracing,
19 |
20 | [Parameter(Mandatory=$false)]
21 | [switch]$WAPTracing,
22 |
23 | [Parameter(Mandatory=$false)]
24 | [switch]$PerfTracing
25 | )
26 |
27 |
28 | ##########################################################################
29 | #region Assembly Depencies
30 | Add-Type -AssemblyName System.ServiceProcess
31 | Add-Type -AssemblyName System.Windows.Forms
32 | Add-Type -AssemblyName System.IO.Compression.FileSystem
33 |
34 | #region Parameters
35 | [Version]$WinVer = [System.Environment]::OSVersion.Version
36 | $IsProxy = ((Get-WindowsFeature -name ADFS-Proxy).Installed -or (Get-WindowsFeature -name Web-Application-Proxy).Installed)
37 |
38 | # Event logs
39 | $ADFSDebugEvents = "Microsoft-Windows-CAPI2/Operational","AD FS Tracing/Debug","Device Registration Service Tracing/Debug"
40 | $WAPDebugEvents = "Microsoft-Windows-CAPI2/Operational","AD FS Tracing/Debug","Microsoft-Windows-WebApplicationProxy/Session"
41 |
42 | $ADFSExportEvents = 'System','Application','Security','AD FS Tracing/Debug','AD FS/Admin','Microsoft-Windows-CAPI2/Operational','Device Registration Service Tracing/Debug','DRS/Admin'
43 | $WAPExportEvents = 'System','Application','Security','AD FS Tracing/Debug','AD FS/Admin','Microsoft-Windows-CAPI2/Operational','Microsoft-Windows-WebApplicationProxy/Admin','Microsoft-Windows-WebApplicationProxy/Session'
44 |
45 | #Definition Netlogon Debug Logging
46 | $setDBFlag = 'DBFlag'
47 | $setvaltype = [Microsoft.Win32.RegistryValueKind]::String
48 | $setvalue = "0x2fffffff"
49 |
50 | # Netlogon increase size to 100MB = 102400000Bytes = 0x61A8000)
51 | $setNLMaxLogSize = 'MaximumLogFileSize'
52 | $setvaltype2 = [Microsoft.Win32.RegistryValueKind]::DWord
53 | $setvalue2 = 0x061A8000
54 |
55 | # Store the original values to revert the config after collection
56 | $orgdbflag = (get-itemproperty -PATH "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters").$setDBFlag
57 | $orgNLMaxLogSize = (get-itemproperty -PATH "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters").$setNLMaxLogSize
58 |
59 | #ETW Trace providers for SSL,kerberos,ntlm,http.sys
60 | $LogmanOn = 'logman.exe create trace "schannel" -ow -o .\schannel.etl -p {37D2C3CD-C5D4-4587-8531-4696C44244C8} 0xffffffffffffffff 0xff -nb 16 16 -bs 1024 -mode Circular -f bincirc -max 1024 -ets',`
61 | 'logman create trace "dcloc" -ow -o .\dcloc_krb_ntlmauth.etl -p "Microsoft-Windows-DCLocator" 0xffffffffffffffff 0xff -nb 16 16 -bs 1024 -mode Circular -f bincirc -max 1024 -ets',`
62 | 'logman update trace "dcloc" -p {6B510852-3583-4E2D-AFFE-A67F9F223438} 0xffffffffffffffff 0xff -ets',`
63 | 'logman update trace "dcloc" -p {5BBB6C18-AA45-49B1-A15F-085F7ED0AA90} 0xffffffffffffffff 0xff -ets',`
64 | 'logman create trace "minio_http" -ow -o .\http_trace.etl -p "Microsoft-Windows-HttpService" 0xffffffffffffffff 0xff -nb 16 16 -bs 1024 -mode Circular -f bincirc -max 2048 -ets',`
65 | 'logman update trace "minio_http" -p "Microsoft-Windows-HttpEvent" 0xffffffffffffffff 0xff -ets',`
66 | 'logman update trace "minio_http" -p "Microsoft-Windows-Http-SQM-Provider" 0xffffffffffffffff 0xff -ets',`
67 | 'logman update trace "minio_http" -p {B3A7698A-0C45-44DA-B73D-E181C9B5C8E6} 0xffffffffffffffff 0xff -ets'
68 |
69 | $LogmanOff = 'logman stop "schannel" -ets',`
70 | 'logman stop "minio_http" -ets',`
71 | 'logman stop "dcloc" -ets'
72 |
73 | #ldap debug traces; process filters are set in the function to enable ldap tracing
74 | $ldapetlOn='logman create trace "adfs_ldap" -ow -o .\ldap.etl -p "Microsoft-Windows-ADSI" 0xffffffffffffffff 0xff -nb 16 16 -bs 1024 -mode Circular -f bincirc -max 4096 -ets',`
75 | 'logman update trace "adfs_ldap" -p "Microsoft-Windows-LDAP-Client" 0xffffffffffffffff 0xff -ets'
76 |
77 | $ldapetlOff= 'logman stop "adfs_ldap" -ets'
78 |
79 | #Web Application Proxy Traces
80 | $WAPTraceOn = 'logman create trace "WebAppProxy" -ow -o .\wap_trace.etl -p {66C13383-C691-4CF7-B404-7E172E2DC0C2} 0xffffffffffffffff 0xff -nb 16 16 -bs 1024 -mode Circular -f bincirc -max 4096 -ets ',`
81 | 'logman update trace "WebAppProxy" -p {7B879E0C-83A7-4DCA-8492-063A257D4288} 0xffffffffffffffff 0xff -ets',`
82 | 'logman update trace "WebAppProxy" -p {DBD9121B-9FC9-4725-B35D-EC411FC28196} 0xffffffffffffffff 0xff -ets',`
83 | 'logman update trace "WebAppProxy" -p {2C7484EA-F1AC-4A4F-8FF0-39222A187F0D} 0xffffffffffffffff 0xff -ets',`
84 | 'logman update trace "WebAppProxy" -p {6519B1CA-2DD1-45D8-A53A-34D03B24EF58} 0xffffffffffffffff 0xff -ets'
85 | $WapTraceOff = "logman -stop WebAppProxy -ets"
86 |
87 | #NetworkCapture+genericInternetTraffic
88 | $EnableNetworkTracer = 'netsh trace start scenario=internetServer capture=yes report=disabled overwrite=yes maxsize=500 tracefile=.\%COMPUTERNAME%-network.etl'
89 | $DisableNetworkTracer = 'netsh trace stop'
90 |
91 | #Performance Counters
92 | $perfcnt = @{
93 | ADFSMain = @{ CounterName = "\AD FS\*"; Type = "ADFSBackend" }
94 | ADFSCrypto = @{ CounterName = "\AD FS Cryptographic Counters(*)\*"; Type = "ADFSBackend" }
95 | ADFSAttStore = @{ CounterName = "\AD FS Attribute Store Counters(*)\*"; Type = "ADFSBackend" }
96 | ADFSDomCount = @{ CounterName = "\AD FS Domain Connection Counters\*"; Type = "ADFSBackend" }
97 | ADFSExtAuth = @{ CounterName = "\AD FS External Authentication Provider Counters\*"; Type = "ADFSBackend" }
98 | ADFSNode2Node = @{ CounterName = "\AD FS Inter-node Communication Counters(*)\*"; Type = "ADFSBackend" }
99 | ADFSLocalClaims = @{ CounterName = "\AD FS Local Claims Provider Connections(*)\*"; Type = "ADFSBackend" }
100 | WIDDBCounter = @{ CounterName = '\MSSQL$MICROSOFT##WID:Databases(*)\*'; Type = "WID" }
101 | ADFSProxy = @{ CounterName = "\AD FS Proxy\*"; Type="ADFSProxy"}
102 | WAPPerf = @{ CounterName = "\Web Application Proxy\*"; Type="ADFSProxy"}
103 | TCPCounter = @{ CounterName = "\TCPv4\*"; Type="General"}
104 | Memory = @{ CounterName = "\Memory\*"; Type="General"}
105 | Processor = @{ CounterName = "\Processor(*)\*"; Type="General"}
106 | Process = @{ CounterName = "\Process(*)\*"; Type="General"}
107 | }
108 |
109 | #Collection for Additional Files
110 | $Filescollector = 'copy /y %windir%\debug\netlogon.* ',`
111 | 'ipconfig /all > %COMPUTERNAME%-ipconfig-all.txt',`
112 | 'nltest /trusted_domains > %COMPUTERNAME%-nltest-trusted_domains.txt',`
113 | 'netsh dnsclient show state > %COMPUTERNAME%-netsh-dnsclient-show-state.txt',`
114 | 'route print > %COMPUTERNAME%-route-print.txt',`
115 | 'netsh advfirewall show global > %COMPUTERNAME%-netsh-int-advf-show-global.txt',`
116 | 'netsh int ipv4 show dynamicport tcp > %COMPUTERNAME%-netsh-int-ipv4-show-dynamicport-tcp.txt',`
117 | 'netsh int ipv4 show dynamicport udp > %COMPUTERNAME%-netsh-int-ipv4-show-dynamicport-udp.txt',`
118 | 'netsh int ipv6 show dynamicport tcp > %COMPUTERNAME%-netsh-int-ipv6-show-dynamicport-tcp.txt',`
119 | 'netsh int ipv6 show dynamicport udp > %COMPUTERNAME%-netsh-int-ipv6-show-dynamicport-udp.txt',`
120 | 'netsh http show cacheparam > %COMPUTERNAME%-netsh-http-show-cacheparam.txt',`
121 | 'netsh http show cachestate > %COMPUTERNAME%-netsh-http-show-cachestate.txt',`
122 | 'netsh http show sslcert > %COMPUTERNAME%-netsh-http-show-sslcert.txt',`
123 | 'netsh http show iplisten > %COMPUTERNAME%-netsh-http-show-iplisten.txt',`
124 | 'netsh http show servicestate > %COMPUTERNAME%-netsh-http-show-servicestate.txt',`
125 | 'netsh http show timeout > %COMPUTERNAME%-netsh-http-show-timeout.txt',`
126 | 'netsh http show urlacl > %COMPUTERNAME%-netsh-http-show-urlacl.txt',`
127 | 'GPResult /f /h %COMPUTERNAME%-GPReport.html',`
128 | 'systeminfo > %COMPUTERNAME%-sysinfo.txt',`
129 | 'regedit /e %COMPUTERNAME%-reg-NTDS-port-and-other-params.txt HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\parameters',`
130 | 'regedit /e %COMPUTERNAME%-reg-NETLOGON-port-and-other-params.txt HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\parameters',`
131 | 'regedit /e %COMPUTERNAME%-reg-schannel.txt HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\SCHANNEL',`
132 | 'regedit /e %COMPUTERNAME%-reg-Cryptography_registry.txt HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography',`
133 | 'regedit /e %COMPUTERNAME%-reg-ciphers_policy_registry.txt HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL'
134 |
135 | #Enum forDotNetReleases
136 | #https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#version_table
137 | #https://learn.microsoft.com/de-de/lifecycle/products/microsoft-net-framework
138 | $fxversions = @{
139 | ".NET Framework 4.5 (all OS)" = 378389
140 | ".NET Framework 4.5.1 (2012R2)" = 378675
141 | ".NET Framework 4.5.1 (Windows other)" = 378758
142 | ".NET Framework 4.5.2 (all OS)" = 379893
143 | ".NET Framework 4.6 (Win 10)" = 393295
144 | ".NET Framework 4.6 (other OS)" = 393297
145 | ".NET Framework 4.6.1 (Win 10 1511)" = 394254
146 | ".NET Framework 4.6.1 (all OS)" = 394271
147 | ".NET Framework 4.6.2 (RS1/2016)" = 394802
148 | ".NET Framework 4.6.2 (all OS)" = 394806
149 | ".NET Framework 4.7 (Win 10 1703/RS2)" = 460798
150 | ".NET Framework 4.7 (all OS)" = 460805
151 | ".NET Framework 4.7.1 (Win 10 1709/RS3)"= 461308
152 | ".NET Framework 4.7.1 (all Other)" = 461310
153 | ".NET Framework 4.7.2 (Win10 1803)" = 461808
154 | ".NET Framework 4.7.2 (all OS)" = 461814
155 | ".NET Framework 4.8 (Win 10 19H1/19H2)" = 528040
156 | ".NET Framework 4.8 (Win 10 20H1-22H2)" = 528372
157 | ".NET Framework 4.8 (Win 11/Server22)" = 528449
158 | ".NET Framework 4.8 (other OS)" = 528049
159 | ".NET Framework 4.8.1 (Win11 2022)" = 533320
160 | ".NET Framework 4.8.1 (other OS) " = 533325
161 | }
162 |
163 | #TypeDefinition for interop with native APIs
164 | Add-Type -TypeDefinition @"
165 | using System;
166 | using System.Text.RegularExpressions;
167 | using System.Runtime.InteropServices;
168 |
169 | public enum AccessType
170 | {
171 | DefaultProxy = 0,
172 | NoProxy = 1,
173 | NamedProxy = 3,
174 | AutomaticProxy = 4
175 | }
176 |
177 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
178 | public struct WINHTTP_PROXY_INFO
179 | {
180 | public AccessType AccessType;
181 | public string Proxy;
182 | public string Bypass;
183 | }
184 |
185 | public struct WinhttpCurrentUserIeProxyConfig
186 | {
187 | [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
188 | public bool AutoDetect;
189 | [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
190 | public string AutoConfigUrl;
191 | [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
192 | public string Proxy;
193 | [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
194 | public string ProxyBypass;
195 | }
196 |
197 | public class WinHttp
198 | {
199 | [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
200 | public static extern bool WinHttpGetDefaultProxyConfiguration(ref WINHTTP_PROXY_INFO config);
201 | [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
202 | public static extern bool WinHttpGetIEProxyConfigForCurrentUser(ref WinhttpCurrentUserIeProxyConfig pProxyConfig);
203 | }
204 | [Flags]
205 | public enum EncTypes
206 | {
207 | NULL_DEFAULTS_TO_RC4_HMAC = 0x0,
208 | DES_CBC_CRC = 0x01,
209 | DES_CBC_MD5 = 0x02,
210 | RC4_HMAC = 0x04,
211 | AES128_CTS_HMAC_SHA1_96 = 0x08,
212 | AES256_CTS_HMAC_SHA1_96 = 0x10,
213 | FAST_Supported = 0x10000,
214 | CompoundIdentity = 0x20000,
215 | Claims_Supported = 0x40000,
216 | Sid_Compression_Disabled = 0x80000
217 | }
218 |
219 | public class KrbEnum
220 | {
221 | public static string[] EnumerateKrb(int encType)
222 | {
223 | EncTypes type = (EncTypes)encType;
224 | string[] result = type.ToString().Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
225 | return result;
226 | }
227 | }
228 |
229 | public class ServiceConfigHelper
230 | {
231 |
232 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
233 | public struct QUERY_SERVICE_CONFIG
234 | {
235 | public int dwServiceType;
236 | public int dwStartType;
237 | public int dwErrorControl;
238 | [MarshalAs(UnmanagedType.LPWStr)]
239 | public string lpBinaryPathName;
240 | public int dwTagId;
241 | [MarshalAs(UnmanagedType.LPWStr)]
242 | public string lpLoadOrderGroup;
243 | public int dwDependencies;
244 | [MarshalAs(UnmanagedType.LPWStr)]
245 | public string lpServiceStartName; // This is the service account name
246 | public IntPtr lpDisplayName;
247 | }
248 |
249 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
250 | public static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, uint dwDesiredAccess);
251 |
252 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
253 | public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
254 |
255 | [DllImport("advapi32.dll", SetLastError = true)]
256 | public static extern bool CloseServiceHandle(IntPtr hSCObject);
257 |
258 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
259 | public static extern bool QueryServiceConfig(IntPtr hService, IntPtr lpServiceConfig, int cbBufSize, out int pcbBytesNeeded);
260 |
261 | const uint SC_MANAGER_ALL_ACCESS = 0xF003F;
262 | const uint SERVICE_QUERY_CONFIG = 0x0001;
263 |
264 | // Method to get the service account name
265 | public static string GetServiceAccount(string serviceName)
266 | {
267 | IntPtr scmHandle = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
268 | if (scmHandle == IntPtr.Zero)
269 | {
270 | return "OpenSCManager_failed";
271 | }
272 |
273 | IntPtr serviceHandle = OpenService(scmHandle, serviceName, SERVICE_QUERY_CONFIG);
274 | if (serviceHandle == IntPtr.Zero)
275 | {
276 | CloseServiceHandle(scmHandle);
277 | return "OpenService_failed";
278 | }
279 |
280 | int bytesNeeded = 0;
281 | QueryServiceConfig(serviceHandle, IntPtr.Zero, 0, out bytesNeeded); // Find out how much memory is needed
282 |
283 | IntPtr queryConfigBuffer = Marshal.AllocHGlobal(bytesNeeded);
284 | bool success = QueryServiceConfig(serviceHandle, queryConfigBuffer, bytesNeeded, out bytesNeeded);
285 | if (!success)
286 | {
287 | Marshal.FreeHGlobal(queryConfigBuffer);
288 | CloseServiceHandle(serviceHandle);
289 | CloseServiceHandle(scmHandle);
290 | return "QueryServiceConfig_failed";
291 | }
292 |
293 | QUERY_SERVICE_CONFIG qsc = (QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(queryConfigBuffer, typeof(QUERY_SERVICE_CONFIG));
294 | string serviceAccount = qsc.lpServiceStartName;
295 |
296 | Marshal.FreeHGlobal(queryConfigBuffer);
297 | CloseServiceHandle(serviceHandle);
298 | CloseServiceHandle(scmHandle);
299 |
300 | return serviceAccount;
301 | }
302 | }
303 | "@
304 | #endregion
305 | ##########################################################################
306 | #region UI
307 | function filepathvalidformat {
308 | param (
309 | $path
310 | )
311 | # Regular expression to match a valid filesystem path format
312 | $filepathreg = '^(?:[a-zA-Z]:\\|\\\\[\d\D]|\.{1,2}\\)([^<>:"\\|?*]+\\)*[^<>:"\\|?*]*$' #'^(?:[a-zA-Z]:\\|\\\\|\.{1,2}\\)([^<>:"\\|?*]+\\)*[^<>:"\\|?*]*$'
313 | return [regex]::IsMatch($path, $filepathreg)
314 | }
315 |
316 | $DisplayText = @(
317 | @{ Text = "This ADFS Tracing script is designed to gather detailed information about your ADFS configuration and related Windows settings. `nIt also offers the ability to collect various debug logs at runtime for issues that need to be actively reproduced or that are not easily detectable through other means.
318 | The collected data can be provided to a Microsoft support technician for further analysis."}
319 | @{ Text = "`nWhen performing a Debug/Runtime Trace, you have the option to include Network Traces and/or Performance Counters in the data collection if needed to troubleshoot a specific issue."}
320 | @{ Text = "`nThe script will prepare to capture data and will notify the Administrator when the data collection process is ready to begin.`nIt will pause and you can setup the tracing in the same way."}
321 | @{ Text = "By pressing 'CTRL + Y' the data collecting process on the server. When Tracing multiple servers repeat the procedure and start the tracing on the other nodes as well"}
322 | @{ Text = "The script will display another message to confirm that it is actively capturing data. `nPress 'CTRL + Y' again to stop the data capture.`n"}
323 | @{ Text = "`nNote:"; Style="bold"}
324 | @{ Text = "The Script is not designed to run for extended periods."}
325 | @{ Text = "In most cases, the script will require between 4GB to 10GB of diskspace, depending on the workload and the duration of the trace and size of the eventlogs."}
326 | @{ Text = "The script will capture multiple traces in circular buffers and will use a temporary folder at the specified path (e.g., C:\tracing\temporary)."}
327 | @{ Text = "It's advisable to capture data during periods of low activity in your ADFS environment to minimize impact."}
328 | @{ Text = "The temporary folder will be later compressed into a .zip file and stored at the selected path."}
329 | )
330 |
331 | function WriteRichBoxText {
332 | param (
333 | [Array]$textElements
334 | )
335 | $Description.text=""
336 | foreach ($element in $textElements) {
337 | $Description.SelectionStart = $Description.TextLength
338 | $Description.SelectionLength = 0
339 | if ($element.Style -eq "bold") {
340 | $font = New-Object System.Drawing.Font('Arial', 10, [System.Drawing.FontStyle]::Bold)
341 | }
342 | else {
343 | $font = New-Object System.Drawing.Font('Arial', 10, [System.Drawing.FontStyle]::Regular)
344 | }
345 | $Description.SelectionFont = $font
346 | if (!$element.Color) {
347 | $element.Color= [System.Drawing.Color]::FromName('black')
348 | }
349 | $Description.SelectionColor = [System.Drawing.Color]::FromName($element.Color)
350 | $Description.AppendText($element.Text + [Environment]::NewLine)
351 | $Description.SelectionStart = 0
352 | }
353 | }
354 |
355 | Function RunDialog {
356 | [System.Windows.Forms.Application]::EnableVisualStyles()
357 |
358 | $Form = New-Object system.Windows.Forms.Form
359 | $Form.ClientSize = '800,600'
360 | $Form.text = "ADFS Trace Collector"
361 | $Form.TopMost = $false
362 | $Form.StartPosition = 'CenterScreen'
363 | $Form.MaximizeBox = $false
364 | $Form.MinimizeBox = $false
365 | $Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Fixed3D
366 |
367 | # Text field
368 | $Description = New-Object system.Windows.Forms.RichTextBox
369 | $Description.Size = new-object System.Drawing.Size(770, 360)
370 | $Description.multiline = $true
371 | $Description.location = New-Object System.Drawing.Point(15,0)
372 | $Description.Font = 'Arial,10'
373 | $Description.ScrollBars = 'Vertical'
374 | $Description.ReadOnly = $true
375 | $Description.DetectUrls = $true
376 | WriteRichBoxText $DisplayText
377 |
378 | $checkBoxWidth = 180 # Width for each checkbox
379 | $xOffset = 10 # Initial X offset for checkbox
380 | $yOffset = 20 # Y offset for aligning checkboxes
381 |
382 | $Scenario = New-Object System.Windows.Forms.GroupBox
383 | $Scenario.Text = "Scenario"
384 | $Scenario.Location = New-Object System.Drawing.Point(15, 375) # Positioned below the RichTextBox
385 | $Scenario.Size = new-object System.Drawing.Size(770, 50)
386 | $cScenario = @("Configuration only", "Runtime Tracing")
387 |
388 | for ($i = 0; $i -lt $cScenario.Length; $i++) {
389 | $checkBox = New-Object System.Windows.Forms.CheckBox
390 | $checkBox.Text = $cScenario[$i]
391 | $checkBox.AutoSize = $true
392 | $checkBox.Location = New-Object System.Drawing.Point(($xOffset + ($i * $checkBoxWidth)), $yOffset)
393 |
394 | switch -Wildcard ($cScenario[$i]) {
395 | "Configuration only" { Set-Variable -Name cfgonly -Value $checkBox -Force }
396 | "Runtime Tracing" { Set-Variable -Name TracingMode -Value $checkBox -Force }
397 | }
398 | $Scenario.Controls.Add($checkBox)
399 | }
400 | # Options GroupBox
401 | $Options = New-Object System.Windows.Forms.GroupBox
402 | $Options.Text = "Options"
403 | $Options.Location = New-Object System.Drawing.Point(15, 430) # Positioned below the ScenarioGroup
404 | $Options.Size = new-object System.Drawing.Size(480, 50) # Adjust the size as needed 780,50
405 |
406 | $cOptions = @("include Network Traces", "include Performance Counter")
407 |
408 | for ($i = 0; $i -lt $cOptions.Length; $i++) {
409 | $checkBox = New-Object System.Windows.Forms.CheckBox
410 | $checkBox.Text = $cOptions[$i]
411 | $checkBox.AutoSize = $true
412 | $checkBox.Enabled= $false
413 | $checkBox.Location = New-Object System.Drawing.Point(($xOffset + ($i * $checkBoxWidth)), $yOffset)
414 |
415 | switch -Wildcard ($cOptions[$i]) {
416 | "include Network Traces" { Set-Variable -Name NetTrace -Value $checkBox -Force }
417 | "include Performance Counter" { Set-Variable -Name perfc -Value $checkBox -Force }
418 | }
419 | $Options.Controls.Add($checkBox)
420 | }
421 | ##### Advanced Options GroupBox
422 | $aOptions = New-Object System.Windows.Forms.GroupBox
423 | $aOptions.Text = if(!$IsProxy){ "advanced Options (can cause service restarts)"} else { "advanced Options" }
424 | $aOptions.Location = New-Object System.Drawing.Point(500, 430) # Positioned below the ScenarioGroup
425 | $aOptions.Size = new-object System.Drawing.Size(285, 50) # Adjust the size as needed
426 |
427 | $caOptions= if(!$IsProxy){ "LDAP Traces" } else { "WAP Traces" }
428 |
429 | for ($i = 0; $i -lt $caOptions.count; $i++) {
430 | $checkBox = New-Object System.Windows.Forms.CheckBox
431 | $checkBox.Text = $caOptions
432 | $checkBox.AutoSize = $true
433 | $checkBox.Enabled= $false
434 | $checkBox.Location = New-Object System.Drawing.Point(($xOffset + ($i * $checkBoxWidth)), $yOffset)
435 |
436 | switch -Wildcard ($caOptions) {
437 | "LDAP Traces" { Set-Variable -Name ldapt -Value $checkBox -Force }
438 | "WAP Traces" { Set-Variable -Name wapt -Value $checkBox -Force }
439 | }
440 | $aOptions.Controls.Add($checkBox)
441 | }
442 |
443 | #####
444 | $label = New-Object System.Windows.Forms.GroupBox
445 | $label.Text = 'Type a path to the Destination Folder or Click "Browse..." to select the Folder'
446 | $label.Location = New-Object System.Drawing.Point(15,480) # Positioned below the ScenarioGroup
447 | $label.Size = new-object System.Drawing.Size(585, 60) # Adjust the size as needed
448 |
449 | #Text Field for the Export Path to store the results
450 | $TargetFolder = New-Object system.Windows.Forms.TextBox
451 | $TargetFolder.text = ""
452 | $TargetFolder.Size = new-object System.Drawing.Size(470, 30)
453 | $TargetFolder.location = New-Object System.Drawing.Point(10,20)
454 | $TargetFolder.Font = 'Arial,13'
455 |
456 | $SelFolder = New-Object system.Windows.Forms.Button
457 | $SelFolder.text = "Browse..."
458 | $SelFolder.Size = new-object System.Drawing.Size(90, 29)
459 | $SelFolder.location = New-Object System.Drawing.Point(486,20)
460 | $SelFolder.Font = 'Arial,10'
461 |
462 | $label.Controls.AddRange(@($TargetFolder,$SelFolder))
463 |
464 | $Okbtn = New-Object system.Windows.Forms.Button
465 | $Okbtn.text = "OK"
466 | $Okbtn.Size = new-object System.Drawing.Size(70, 30)
467 | $Okbtn.location = New-Object System.Drawing.Point(620,540)
468 | $Okbtn.Font = 'Arial,10'
469 | $Okbtn.DialogResult = [System.Windows.Forms.DialogResult]::OK
470 | $Okbtn.Enabled = $false
471 |
472 | $cnlbtn = New-Object system.Windows.Forms.Button
473 | $cnlbtn.text = "Cancel"
474 | $cnlbtn.Size = new-object System.Drawing.Size(70, 30)
475 | $cnlbtn.location = New-Object System.Drawing.Point(700,540)
476 | $cnlbtn.Font = 'Arial,10'
477 | $cnlbtn.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
478 |
479 | $Form.controls.AddRange(@($Description,$Scenario,$Options,$aOptions,$Okbtn,$cnlbtn,$label))
480 |
481 | $cfgonly.Add_CheckStateChanged({ if ($cfgonly.checked) {
482 | $TracingMode.Enabled = $false;
483 | $NetTrace.Enabled = $false;
484 | $perfc.Enabled = $false;
485 | if(!$IsProxy){ $ldapt.Enabled=$false}else {$wapt.Enabled = $false}
486 | }
487 | else {
488 | $TracingMode.Enabled = $true; $NetTrace.Enabled = $false
489 | }
490 | })
491 |
492 | $TracingMode.Add_CheckStateChanged({ if ($TracingMode.checked){
493 | $cfgonly.Enabled = $false;
494 | $NetTrace.Enabled = $true;
495 | $NetTrace.Checked = $true;
496 | $perfc.Enabled = $true;
497 | if(!$IsProxy){ $ldapt.Enabled=$true}else {$wapt.Enabled = $true}
498 | }
499 | else {
500 | $cfgonly.Enabled = $true;
501 | $NetTrace.Checked = $false;
502 | $NetTrace.Enabled = $false;
503 | $perfc.Checked = $false;
504 | $perfc.Enabled = $false;
505 | if(!$IsProxy) {
506 | $ldapt.Checked = $false;
507 | $ldapt.Enabled = $false;
508 | }
509 | else {
510 | $wapt.Checked = $false;
511 | $wapt.Enabled = $false;
512 | }
513 | }
514 | })
515 |
516 | #For future Versions we may add addional dependencies to the Network Trace.
517 | #$NetTrace.Add_CheckedChanged({ })
518 | $Description.add_LinkClicked({ Start-Process -FilePath $_.LinkText })
519 |
520 | $SelFolder.Add_Click({ $FolderDialog = New-Object windows.forms.FolderBrowserDialog
521 | $FolderDialog.RootFolder = "Desktop"
522 | $FolderDialog.ShowDialog()
523 | $TargetFolder.text = $FolderDialog.SelectedPath
524 | })
525 |
526 | $TargetFolder.Add_TextChanged({ $Okbtn.Enabled = filepathvalidformat $TargetFolder.Text; })
527 | $FormsCompleted = $Form.ShowDialog()
528 |
529 | if ($FormsCompleted -eq [System.Windows.Forms.DialogResult]::OK) {
530 | return New-Object psobject -Property @{
531 | Path = $TargetFolder.text
532 | TraceEnabled = $TracingMode.Checked
533 | NetTraceEnabled = $NetTrace.Checked
534 | ConfigOnly = $cfgonly.Checked
535 | PerfCounter =$perfc.Checked
536 | LdapTraceEnabled=$ldapt.Checked
537 | WAPTraceEnabled=$wapt.Checked
538 | }
539 | $Form.dispose()
540 | }
541 | elseif($FormsCompleted -eq [System.Windows.Forms.DialogResult]::Cancel) {
542 | Write-host "Script was canceled by User" -ForegroundColor Red
543 | $Form.dispose()
544 | exit
545 | }
546 | }
547 |
548 | Function Pause { param([String]$Message,[String]$MessageTitle,[String]$MessageC)
549 | # "ReadKey" not supported in PowerShell ISE.
550 | If ($psISE) {
551 | # Show MessageBox UI instead
552 | $Shell = New-Object -ComObject "WScript.Shell"
553 | $Shell.Popup($Message, 0, $MessageTitle, 0)|Out-Null
554 | Return
555 | }
556 | #If not ISE we prompt for key stroke
557 | Write-Host -NoNewline $MessageC -ForegroundColor Yellow
558 | do {$keyInfo = [Console]::ReadKey($false)} until ($keyInfo.Key -eq 'Y' -and $keyInfo.Modifiers -eq 'Control')
559 | }
560 | #endregion
561 | ##########################################################################
562 | #region Functions
563 | Function IsAdminAccount {
564 | return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
565 | }
566 |
567 | function LDAPQuery {
568 | param(
569 | [string]$filter,
570 | [string[]]$att,
571 | [string]$conn,
572 | [string]$basedn
573 | )
574 | [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols") | Out-Null
575 | [System.Reflection.Assembly]::LoadWithPartialName("System.Net")| Out-Null
576 |
577 | $c = New-Object System.DirectoryServices.Protocols.LdapConnection ($conn)
578 |
579 | $c.SessionOptions.SecureSocketLayer = $false;
580 | $c.SessionOptions.Sealing = $true
581 | $c.SessionOptions.Signing = $true
582 | $c.AuthType = [System.DirectoryServices.Protocols.AuthType]::Kerberos
583 | $c.Bind();
584 | #rather timeout than waiting for too long...
585 | $c.Timeout=[timespan]::FromSeconds(45)
586 |
587 | if([string]::IsNullOrEmpty($basedn)) {
588 | $basedn = (New-Object System.DirectoryServices.DirectoryEntry("LDAP://$conn/RootDSE")).DefaultNamingContext
589 | }
590 |
591 | $scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
592 | $r = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $basedn,$filter,$scope,$att
593 |
594 | $re = try { $c.SendRequest($r) }
595 | catch { $_.Exception.InnerException }
596 |
597 | $c.Dispose()
598 |
599 | return $re
600 | }
601 |
602 | function get-Certificatesfromstore {
603 | param(
604 | [string]$StoreName
605 | )
606 |
607 | $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName,[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
608 | $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
609 | $certcollection = $store.Certificates
610 | $store.Close()
611 |
612 | return $certcollection
613 | }
614 |
615 | function Get-CertificatesByStore {
616 | param (
617 | [Parameter(Mandatory=$true)]
618 | [ValidateSet("My", "Root", "CA", "NtAuth", "ADFSTrustedDevices", "ClientAuthIssuer")]
619 | [string]$StoreName
620 | )
621 |
622 | $storePath = switch ($StoreName) {
623 | "My" { [System.Security.Cryptography.X509Certificates.StoreName]::My }
624 | "Root" { [System.Security.Cryptography.X509Certificates.StoreName]::Root }
625 | "CA" { 'CA' } # [System.Security.Cryptography.X509Certificates.StoreName]::CertificateAuthority seems to be not working so using the Alias CA instead
626 | "NtAuth" { 'NtAuth' }
627 | "ADFSTrustedDevices" { 'ADFSTrustedDevices' }
628 | "ClientAuthIssuer" { 'ClientAuthIssuer' }
629 | }
630 |
631 | $certs = get-Certificatesfromstore $storePath
632 | $mycert = @()
633 |
634 | foreach ($cert in $certs) {
635 | $obj = New-Object -TypeName PSObject
636 | $obj | Add-Member -MemberType NoteProperty -Name "Issuer" -Value $cert.Issuer
637 | if(($cert.FriendlyName)){$obj | Add-Member -MemberType NoteProperty -Name "FriendlyName" -Value $cert.FriendlyName}
638 | $obj | Add-Member -MemberType NoteProperty -Name "Subject" -Value $cert.Subject
639 | $obj | Add-Member -MemberType NoteProperty -Name "NotAfter" -Value $cert.NotAfter
640 | $obj | Add-Member -MemberType NoteProperty -Name "NotBefore" -Value $cert.NotBefore
641 | $obj | Add-Member -MemberType NoteProperty -Name "SerialNumber" -Value $cert.SerialNumber
642 | $obj | Add-Member -MemberType NoteProperty -Name "ThumbPrint" -Value $cert.Thumbprint
643 |
644 | # PrivateKey and related properties for MY store
645 | if ($StoreName -eq "My") {
646 | $obj | Add-Member -MemberType NoteProperty -Name "PrivateKey" -Value $cert.HasPrivateKey
647 | $obj | Add-Member -MemberType NoteProperty -Name "Exportable" -Value $cert.PrivateKey.CspKeyContainerInfo.Exportable
648 | $obj | Add-Member -MemberType NoteProperty -Name "ProviderName" -Value $cert.PrivateKey.CspKeyContainerInfo.ProviderName
649 |
650 | $keyspec = (($cert.PrivateKey).CspKeyContainerInfo).KeyNumber
651 | $keyspecName = switch ($keyspec) {
652 | "Exchange" { "AT_EXCHANGE" }
653 | "Signature" { "AT_SIGNATURE" }
654 | default { "CNG" }
655 | }
656 | $obj | Add-Member -MemberType NoteProperty -Name "Keyspec" -Value $keyspecName
657 | }
658 |
659 | # Root/Non-Root check and origin determination for other stores
660 | if ($StoreName -ne "My") {
661 | if ($cert.Subject -ne $cert.Issuer) {
662 | $obj | Add-Member -MemberType NoteProperty -Name "IsRoot" -Value 'Non-Root'
663 | } else {
664 | $obj | Add-Member -MemberType NoteProperty -Name "IsRoot" -Value 'Root'
665 | }
666 |
667 | # Determine the origin based on store type
668 | $certsrc = $null
669 | if ($StoreName -eq "NtAuth") { $obj | Add-Member -MemberType NoteProperty -Name "Origin" -Value 'DirectoryService' }
670 | elseif ($StoreName -eq "ADFSTrustedDevices") { $obj | Add-Member -MemberType NoteProperty -Name "Origin" -Value 'ADFS' }
671 | elseif ($StoreName -eq "ClientAuthIssuer") { $obj | Add-Member -MemberType NoteProperty -Name "Origin" -Value 'Registry' }
672 | else {
673 | $ds = "HKLM:\SOFTWARE\Microsoft\EnterpriseCertificates\$StoreName\Certificates\" + $cert.Thumbprint
674 | $gpo = "HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\$StoreName\Certificates\" + $cert.Thumbprint
675 |
676 | if ([bool](Test-Path $ds)) { $certsrc = "DirectoryService" }
677 | if ([bool](Test-Path $gpo)) { $certsrc = "GroupPolicy" }
678 | if (![string]::IsNullOrEmpty($certsrc)) { $obj | Add-Member -MemberType NoteProperty -Name "Origin" -Value $certsrc }
679 | else { $obj | Add-Member -MemberType NoteProperty -Name "Origin" -Value 'Registry' }
680 | }
681 | }
682 |
683 | $mycert += $obj
684 | }
685 |
686 | # We check ClientAuthIssuer only if a bidning was configured. An empty store can cause issues so warn.
687 | if ($StoreName -eq "ClientAuthIssuer" -and $mycert.Count -eq 0) {
688 | $mycert = "WARNING: ClientAuthIssuers is configured on an ADFS related binding but the Certificate store is empty. This can break Certificate Based authentication for users"
689 | }
690 |
691 | return $mycert
692 | }
693 |
694 |
695 | function Test-IsWID {
696 | # Try to get the SecurityTokenService object and its configuration DB connection string
697 | $sts = Get-WmiObject -Namespace root\ADFS -Class SecurityTokenService -ErrorAction SilentlyContinue
698 | $connectionString = $sts.ConfigurationDatabaseConnectionString
699 | # Determine if it's using WID or SSEE
700 | $result = $connectionString -match "##wid" -or $connectionString -match "##ssee"
701 | #if Wid get the service status and service account name
702 | if ($result) {
703 | $svc = new-object System.ServiceProcess.ServiceController('MSSQL$MICROSOFT##WID')
704 | $widaccount = [ServiceConfigHelper]::GetServiceAccount($svc.Name)
705 | }
706 |
707 | return [PSCustomObject]@{
708 | IsWID = $result
709 | ConfigurationDatabaseConnectionString = $connectionString
710 | IsWIDStarted = $svc.Status
711 | WIDServiceAccount = $widaccount
712 | }
713 | }
714 |
715 | function Get-ADFSDBStateFromWID {
716 |
717 | $dbconfig = Test-IsWID
718 | $dbstates = @{}
719 |
720 | #skip if not WID exit.we shouldnt get this ever since we check before calling this function
721 | if (!$dbconfig.IsWID) {
722 | break
723 | }
724 |
725 | #Verify Service Account for WIDService
726 | $svcstatustmpl= @'
727 | ================ WID Service - Status =================
728 | Service Status : {0}
729 | WID Service Account : {1}
730 | '@
731 | $expectedAccount = 'NT SERVICE\MSSQL$MICROSOFT##WID'
732 | if (!($dbconfig.WIDServiceAccount.IndexOf('_failed') -eq -1)){
733 | $serviceacc=[string]::Format("Error: An error occurred whilst querying the ServiceAccountName for the Windows Internal Database service. Error Code: {0}`r`n", $dbconfig.WIDServiceAccount)
734 | } elseif ([string]::Compare($expectedAccount, $dbconfig.WIDServiceAccount, $true) -eq 0 ) {
735 | $serviceacc=[string]::Format("{0} - Test passed`r`n", $dbconfig.WIDServiceAccount)
736 | } else {
737 | $serviceacc=[string]::Format("{0} - Test failed. Expected Account is: {1}`r`n", $dbconfig.WIDServiceAccount, $expectedAccount)
738 | }
739 |
740 | [string]::Format($svcstatustmpl,
741 | $(switch ([int]$dbconfig.IsWIDStarted) {
742 | ([int][System.ServiceProcess.ServiceControllerStatus]::Running) { "Running - Test passed" }
743 | ([int][System.ServiceProcess.ServiceControllerStatus]::Stopped) { "Stopped - Test failed" }
744 | ([int][System.ServiceProcess.ServiceControllerStatus]::Paused) { "Paused - Test failed" }
745 | ([int][System.ServiceProcess.ServiceControllerStatus]::StartPending) { "Start Pending - Test failed" }
746 | ([int][System.ServiceProcess.ServiceControllerStatus]::StopPending) { "Stop Pending - Test failed" }
747 | default { "Unknown Status - Test failed" }
748 | }),
749 | $serviceacc
750 | )
751 |
752 | #check if service is running and if then attempt the query
753 | if ($dbconfig.IsWIDStarted -eq [System.ServiceProcess.ServiceControllerStatus]::Running ) {
754 |
755 | #query basic DB states also retrieve the owner of the DB.
756 | #general reference on the properties https://learn.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-databases-transact-sql?view=sql-server-ver17
757 | $query=@"
758 | SELECT
759 | d.name AS DatabaseName,
760 | suser_sname(d.owner_sid) AS DatabaseOwner,
761 | d.state_desc AS DatabaseState,
762 | d.recovery_model_desc AS RecoveryModel,
763 | d.is_read_only AS IsReadOnly,
764 | d.is_broker_enabled AS IsBrokerEnabled,
765 | d.user_access_desc AS AccessMode
766 | FROM sys.databases AS d
767 | WHERE d.name LIKE 'ADFS%'
768 | ORDER BY d.name;
769 | "@
770 |
771 | $dbstatustemplate= @'
772 | ============= {0} - Status ============
773 | Database Access : {1}
774 | Database State : {2}
775 | Broker enabled : {3}
776 | Database Owner : {4}
777 |
778 | '@
779 |
780 | $failed = "Test failed"
781 | $success= "Test passed"
782 | $padding=16
783 |
784 | try {
785 | $connection = new-object system.data.SqlClient.SqlConnection($dbconfig.ConfigurationDatabaseConnectionString);
786 | $connection.Open()
787 |
788 | $sqlcmd = $connection.CreateCommand();
789 | $sqlcmd.CommandText = $query;
790 | $result = $sqlcmd.ExecuteReader();
791 | $table = new-object "System.Data.DataTable"
792 | $table.Load($result)
793 |
794 | foreach ($row in $table.Rows) {
795 | $dbstates[$row.DatabaseName] = @{
796 | DatabaseState = $row.DatabaseState
797 | RecoveryModel = $row.RecoveryModel
798 | IsReadOnly = $row.IsReadOnly
799 | AccessMode = $row.AccessMode
800 | BrokerEnabled = $row.IsBrokerEnabled
801 | DBOwner = $row.DatabaseOwner
802 | }
803 | }
804 | } catch {
805 | return [string]::Format($errtemplate, $_.Exception.InnerException)
806 | } finally {
807 | # Close and dispose the connection if it exists
808 | if ($connection.State -eq 'Open') {
809 | $connection.Close()
810 | }
811 | $connection.Dispose()
812 | }
813 | } else {
814 |
815 | $errtemplate= @"
816 | Error: Failed to query ADFS database informations from WID.
817 | Details: {0}
818 | "@
819 | return $([string]::Format($errtemplate, 'WID (Windows Internal Database) service is not running.'))
820 | }
821 |
822 | #loop through the results and format the output
823 | foreach ($dbname in $dbstates.keys) {
824 | #access mode is expected to be MULTI_USER. if SINGLE_USER or RESTRICTED_USER is found we show it as failed
825 | $accmode= switch ($dbstates[$dbname].AccessMode) {
826 | MULTI_USER { "$($dbstates[$dbname].AccessMode.PadRight($padding)) - $($success)"}
827 | SINGLE_USER { "$($dbstates[$dbname].AccessMode.PadRight($padding)) - $($failed)" }
828 | RESTRICTED_USER { "$($dbstates[$dbname].AccessMode.PadRight($padding)) - $($failed)" }
829 | }
830 | # we expect the DB to be online. if any other value is found we show it as failed test
831 | $dbstate= switch ($dbstates[$dbname].DatabaseState -eq "Online" ) {
832 | true {"$($dbstates[$dbname].DatabaseState.PadRight($padding)) - $($success)"}
833 | false {"$($dbstates[$dbname].DatabaseState.PadRight($padding)) - $($failed)" }
834 | }
835 | #broker is expected to be enabled. Else the service may not reload its config after a change was detected (eg after sync)
836 | $broker = switch ([bool]$dbstates[$dbname].BrokerEnabled) {
837 | true {"$($dbstates[$dbname].BrokerEnabled.tostring().PadRight($padding)) - $($success)"}
838 | false {"$($dbstates[$dbname].BrokerEnabled.tostring().PadRight($padding)) - $($failed)"}
839 | }
840 |
841 | #format the output
842 | [string]::Format($dbstatustemplate,
843 | ($dbname.PadRight(19)),
844 | $accmode,
845 | $dbstate,
846 | $broker,
847 | $dbstates[$dbname].DBOwner
848 | )
849 | }
850 | }
851 |
852 | function get-servicesettingsfromdb {
853 | param(
854 | [Parameter(Mandatory=$true)]
855 | [string]$DBConnectionString
856 | )
857 | # Validate input
858 | if ([string]::IsNullOrEmpty($DBConnectionString)) {
859 | $errMsg = "Error: Database connection string is null or empty."
860 | throw [System.ArgumentException]::new($errMsg)
861 | }
862 |
863 | #basic validation of the connection string format
864 | try {
865 | # Attempt to parse the connection string
866 | $dbstring = New-Object System.Data.SqlClient.SqlConnectionStringBuilder $DBConnectionString
867 | }
868 | catch {
869 | $errMsg = "Error: Invalid connection string format"
870 | throw [System.ArgumentException]::new($errMsg,$($_.Exception))
871 | }
872 | #Create SQL Connection
873 | try {
874 | $connection = new-object system.data.SqlClient.SqlConnection($dbstring.ConnectionString);
875 | $connection.Open()
876 |
877 | $query = "SELECT * FROM IdentityServerPolicy.ServiceSettings"
878 | $sqlcmd = $connection.CreateCommand();
879 | $sqlcmd.CommandText = $query;
880 | $result = $sqlcmd.ExecuteReader();
881 | $table = new-object "System.Data.DataTable"
882 | $table.Load($result)
883 | [XML]$SSD= $table.ServiceSettingsData
884 | } catch {
885 | $errMsg = "Error: Failed to connect to or query from ADFS Configuration database"
886 | throw [System.Exception]::new($errMsg,$($_.Exception))
887 |
888 | } finally {
889 | if ($connection.State -eq 'Open') {
890 | $connection.Close()
891 | }
892 | $connection.Dispose()
893 | }
894 |
895 | return $SSD
896 | }
897 |
898 | Function Test-WiaSupportedUseragents {
899 | # Unsupported user agents may cause issues with WIA authentication on various platforms if internal. In particular mobile apps using webview controls or devices that dont support wia per se
900 | # usually this is due to using too generic user agents. Update the list as needed. We will extend this list as needed
901 | $unsupported=@("Mozilla/5.0","Mozilla/4.0","Chrome","FireFox","Safari","Opera","OPR","Edg/*","Edge/*","Edg/","Edge/","Webkit/","=~Windows\s*NT.*Edge")
902 | $agents = (get-adfsproperties).WiasupportedUseragents
903 | $commonItems=@()
904 |
905 | if (!($null -eq $agents) -and !($null -eq $unsupported)) {
906 | $commonItems = [System.Linq.Enumerable]::Intersect(
907 | [System.Collections.Generic.List[object]]@($unsupported),
908 | [System.Collections.Generic.List[object]]@($agents)
909 | )
910 | }
911 |
912 | if ($commonItems.Count -gt 0) {
913 | $commonItemsArray = [System.Linq.Enumerable]::ToArray(
914 | [System.Collections.Generic.IEnumerable[object]]$commonItems
915 | )
916 |
917 | $sb = New-Object System.Text.StringBuilder
918 | for ($i = 0; $i -lt $commonItemsArray.Length; $i++) {
919 | [void]$sb.Append(" - ")
920 | [void]$sb.AppendLine([string]$commonItemsArray[$i])
921 | }
922 |
923 | $msgvalue = [String]::Format(
924 | "Warning: The following WiaSupportUseragents are known to cause unexpected signin issues with certain device platforms:`r`n{0}`The agent string(s) listed are either too generic, lacking device platforms identifiers
925 | or are generally outdated and no longer applicable",$sb)
926 |
927 | $message = New-Object -TypeName PSObject
928 | $message | Add-Member -NotePropertyName 'Test-WiaSupportedUseragents' -NotePropertyValue $msgvalue
929 | } else {
930 |
931 | $msgvalue = [String]::Format("Informational: WiasupportedUseragents seems to be configured correctly.
932 | If a misconfiguration exists it is currently not known by this script.")
933 | $message = New-Object -TypeName PSObject
934 | $message | Add-Member -NotePropertyName 'Test-WiaSupportedUseragents' -NotePropertyValue $msgvalue
935 | }
936 |
937 | return $message
938 | }
939 |
940 |
941 | function Get-AzureMFAConfig {
942 | $dbconfig = Test-IsWID
943 | #skip if WID is not started as it would definitely fail the query
944 | if ($dbconfig.IsWID -and ($dbconfig.IsWIDStarted -ne [System.ServiceProcess.ServiceControllerStatus]::Running )) {
945 | $errMsg = "Error: WID (Windows Internal Database) service is not running."
946 | throw [System.Exception]::new($errMsg)
947 | }
948 |
949 | try {
950 | $ssd = get-servicesettingsfromdb -DBConnectionString $dbconfig.ConfigurationDatabaseConnectionString
951 | } catch {
952 | $errMsg = "Error: Reading Azure MFA Configuration failed."
953 | throw [System.Exception]::new($errMsg,$($_.Exception))
954 | }
955 | if(!$null -eq $ssd) { #loop through the AuthAdapters and find the config for AzureMFAAdapter; we might expand this for other adapters if necessary
956 | foreach ($AmD in $ssd.ServiceSettingsData.SecurityTokenService.AuthenticationMethods.AuthenticationMethodDescriptor) {
957 | if ($AmD.Identifier -eq "AzureMfaAuthentication" -and (!$AmD.ConfigurationData.IsEmpty)) {
958 | return [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($AmD.ConfigurationData))
959 | }
960 | }
961 | }
962 | }
963 |
964 | function Get-ADFSAzureMfaAdapterconfig {
965 | #exception format template
966 | $errmsgformatter=@"
967 | Error: An error occurred whilst attempting to read the MFA Adapter Configuration.
968 | {0}
969 | {1}
970 | {2}
971 | "@
972 | #try to get config and handle the exception if it occurs try to provide as much info as possible and break on error
973 | Try {
974 | $MFAraw= Get-AzureMFAConfig
975 | } Catch {
976 | #cycle through the exception chain to provide as much info as possible
977 | $errstr= [string]::Format($errmsgformatter,
978 | $_.Exception.Message,
979 | $_.Exception.InnerException.Message,
980 | $_.Exception.InnerException.InnerException
981 | )
982 | Return $errstr.TrimEnd()
983 | break
984 | }
985 |
986 | #try to process the config if it was retrieved
987 | if($null -ne $MFAraw) {
988 | $obj = [PSCustomObject]@{}
989 | $obj| Add-Member -MemberType NoteProperty -Name 'AdapterConfig' -Value $MFAraw
990 |
991 | if(($MFAraw -as [XML]).ChildNodes.ClientId -ne '981f26a1-7f43-403b-a875-f8b09b8cd720') {
992 | $obj| Add-Member -MemberType NoteProperty -Name 'Error' -Value 'The configured ClientId is incorrect and does not match the Azure AD MFA ClientId required. Re-run the MFA AdapterConfig and update the ClientID'
993 | }
994 |
995 | $mfacert= (get-childitem Cert:\LocalMachine\my | where-object {$_.Subject -contains "CN="+ ($MFAraw -as [XML]).ChildNodes.TenantId +", OU=Microsoft AD FS Azure MFA"})
996 |
997 | if(![string]::IsNullOrEmpty($mfacert)) {
998 | $obj| Add-Member -MemberType NoteProperty -Name 'Information' -Value 'A suitable Azure MFA Certificate was found in the store. Verify that the certificate referenced below is properly registered in AzureAD'
999 | $obj| Add-Member -MemberType NoteProperty -Name 'Subject' -Value $mfacert.Subject
1000 | $obj| Add-Member -MemberType NoteProperty -Name 'Thumbprint' -Value $mfacert.Thumbprint
1001 | $obj| Add-Member -MemberType NoteProperty -Name 'NotAfter' -Value $mfacert.NotAfter
1002 | $obj| Add-Member -MemberType NoteProperty -Name 'NotBefore' -Value $mfacert.NotBefore
1003 | }
1004 | else {
1005 | $mfacert= get-childitem Cert:\LocalMachine\my | where-object { $_.Subject -match 'OU=Microsoft AD FS Azure MFA' }
1006 | if($mfacert.count -eq '0') {
1007 | $obj| Add-Member -MemberType NoteProperty -Name 'Critical' -Value 'There are no Azure MFA Certificates existing in the local machines store'
1008 | }
1009 |
1010 | if($mfacert.count -eq '1') {
1011 | $obj| Add-Member -MemberType NoteProperty -Name 'Warning' -Value 'A Certificate was found in store but it does not match the TenantId in the configuration'
1012 | $obj| Add-Member -MemberType NoteProperty -Name 'Subject' -Value $mfacert.Subject
1013 | $obj| Add-Member -MemberType NoteProperty -Name 'Thumbprint' -Value $mfacert.Thumbprint
1014 | }
1015 |
1016 | if($mfacert.count -gt '1') {
1017 | $obj| Add-Member -MemberType NoteProperty -Name 'Warning' -Value 'More than one suitable Certificate was found in store but none of them matches the TenantId in the configuration'
1018 | }
1019 | }
1020 |
1021 | $adfsreg = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SOFTWARE\Microsoft\ADFS")
1022 |
1023 | if ($null -ne $adfsreg) {
1024 | $MFAREG = @('StsUrl','SasUrl','ResourceUri')
1025 | $foundKeys = @()
1026 |
1027 | foreach ($key in $MFAREG) {
1028 | if ($null -ne $adfsreg.GetValue($key)) {
1029 | $foundKeys += $key
1030 | }
1031 | }
1032 |
1033 | if ($foundKeys.Count -eq 0) {
1034 | $obj | Add-Member -MemberType NoteProperty -Name 'TenantEnvironment' -Value 'Azure MFA has not been configured for Azure Government and will use the default Public environment.'
1035 | }
1036 | else {
1037 | $obj | Add-Member -MemberType NoteProperty -Name 'TenantEnvironment' -Value 'Registry Entries for Azure Government have been found. Please review the registry'
1038 |
1039 | foreach ($key in $MFAREG) {
1040 | $value = $adfsreg.GetValue($key)
1041 | $obj | Add-Member -MemberType NoteProperty -Name $key -Value $(if ($null -ne $value) { $value } else { 'Key/Value not found' })
1042 | }
1043 | }
1044 | #never forget to close the registry handle
1045 | $adfsreg.Close()
1046 | }
1047 | else {
1048 | $obj | Add-Member -MemberType NoteProperty -Name 'TenantEnvironment' -Value 'Error: ADFS registry key not found'
1049 | }
1050 | return $obj
1051 | }
1052 | else { return "Information: AzureMFA is not configured in this ADFS Farm." }
1053 | }
1054 |
1055 | function Get-ProxySettings {
1056 | $proxycfg = [PSCustomObject]@{}
1057 |
1058 | $IEProxyConfig = New-Object WinhttpCurrentUserIeProxyConfig
1059 | [WinHttp]::WinHttpGetIEProxyConfigForCurrentUser([ref]$IEProxyConfig) |Out-Null
1060 |
1061 | $WINHTTPPROXY = New-Object WINHTTP_PROXY_INFO
1062 | [WinHttp]::WinHttpGetDefaultProxyConfiguration([ref]$WINHTTPPROXY) |Out-Null
1063 |
1064 | $proxycfg = @"
1065 | IE ProxySetting of current CurrentUser: [$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)]
1066 | =============================================================
1067 | AutoDetect: $($IEProxyConfig.AutoDetect)
1068 | AutoConfigUrl: $($IEProxyConfig.AutoConfigUrl)
1069 | ProxyName: $($IEProxyConfig.Proxy)
1070 | ProxyBypass: $($IEProxyConfig.ProxyBypass)
1071 |
1072 | WinHTTP Proxy Setting
1073 | =============================================================
1074 | AutoDetect: $($WINHTTPPROXY.AccessType)
1075 | ProxyName: $($WINHTTPPROXY.Proxy)
1076 | ProxyBypass: $($WINHTTPPROXY.Bypass)
1077 | "@
1078 |
1079 | return $proxycfg
1080 | }
1081 |
1082 | Function EnableDebugEvents ($events) {
1083 | if($TraceEnabled) {
1084 | ForEach ($evt in $events) {
1085 | $TraceLog = New-Object System.Diagnostics.Eventing.Reader.EventlogConfiguration $evt
1086 | $TraceLog.IsEnabled = $false
1087 | $TraceLog.SaveChanges()
1088 |
1089 | if ($TraceLog.LogName -like "*Tracing/Debug*") {
1090 | $TraceLog.ProviderLevel = 5
1091 | $TraceLog.IsEnabled = $true
1092 | $TraceLog.SaveChanges()
1093 | }
1094 | elseif($TraceLog.IsEnabled -eq $false) {
1095 | $tracelog.MaximumSizeInBytes = '50000000'
1096 | $TraceLog.IsEnabled = $true
1097 | $TraceLog.SaveChanges()
1098 | }
1099 | }
1100 | }
1101 | else
1102 | { Write-Host "Debug Event Logging skipped due to selected scenario" -ForegroundColor DarkCyan }
1103 | }
1104 |
1105 | Function LogManStart {
1106 | if($TraceEnabled) {
1107 | Push-Location $TraceDir
1108 | ForEach ($ets in $LogmanOn)
1109 | {
1110 | cmd /c $ets |Out-Null
1111 | }
1112 | Pop-Location
1113 | }
1114 | else { Write-Host "ETW Tracing skipped due to selected scenario" -ForegroundColor DarkCyan }
1115 | }
1116 |
1117 | Function EnableNetlogonDebug {
1118 | if($TraceEnabled) {
1119 | $key = (get-item -PATH "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon")
1120 | $subkey = $key.OpenSubKey("Parameters",$true)
1121 | Write-host "Enabling Netlogon Debug Logging" -ForegroundColor DarkCyan
1122 |
1123 | $subkey.SetValue($setDBFlag,$setvalue,$setvaltype)
1124 |
1125 | Write-host "Increasing Netlogon Debug Size to 100 MB" -ForegroundColor DarkCyan
1126 | $subkey.SetValue($setNLMaxLogSize,$setvalue2,$setvaltype2)
1127 |
1128 | #cleanup and close the write handle
1129 | $key.Close()
1130 | }
1131 | else { Write-Host "Netlogon Logging skipped due to scenario" -ForegroundColor DarkCyan }
1132 | }
1133 |
1134 | Function LogManStop {
1135 | if($TraceEnabled) {
1136 | Push-Location $TraceDir
1137 | ForEach ($log in $LogmanOff) {
1138 | cmd.exe /c $log |Out-Null
1139 | }
1140 | Pop-Location
1141 | }
1142 | else { Write-host "ETW Tracing was not enabled" -ForegroundColor DarkCyan }
1143 | }
1144 |
1145 | Function DisableNetlogonDebug {
1146 | if($TraceEnabled) {
1147 | $key = (get-item -PATH "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon")
1148 | $subkey = $key.OpenSubKey("Parameters",$true)
1149 | # Configure Keys based on initial configuration; if the keys did not exist we are also removing the keys again. else we set the old value
1150 | if ([string]::IsNullOrEmpty($orgdbflag)) { $subkey.deleteValue($setDBFlag) }
1151 | else { $subkey.SetValue($setDBFlag,$orgdbflag,$setvaltype) }
1152 |
1153 | if ([string]::IsNullOrEmpty($orgNLMaxLogSize)) { $subkey.deleteValue($setNLMaxLogSize) }
1154 | else { $subkey.SetValue($setNLMaxLogSize,$orgNLMaxLogSize,$setvaltype2) }
1155 | $key.Close()
1156 | }
1157 | else { Write-host "Netlogon logging was not enabled" -ForegroundColor DarkCyan }
1158 | }
1159 |
1160 | Function DisableDebugEvents ($events) {
1161 | if($TraceEnabled) {
1162 | ForEach ($evt in $events) {
1163 | $TraceLog = New-Object System.Diagnostics.Eventing.Reader.EventlogConfiguration $evt
1164 | if ($TraceLog.IsEnabled -eq $true) {
1165 | $TraceLog.IsEnabled = $false
1166 | $TraceLog.SaveChanges()
1167 | }
1168 | }
1169 | }
1170 | else { Write-host "Debug Tracing Eventlogs were not enabled" -ForegroundColor DarkCyan }
1171 | }
1172 |
1173 | Function ExportEventLogs {
1174 | Param(
1175 | [parameter(Position=0)]
1176 | $events,
1177 | [parameter(Position=1)]
1178 | $RuntimeInMsec
1179 | )
1180 |
1181 | Push-Location $TraceDir
1182 | ForEach ($evts in $events) {
1183 | $expfilter= '*' #default filter
1184 | #Sec events can be very large; in tracing mode we only care about the events whilst the trace ran
1185 | #query filter for export is timebased and calculated on the time the trace collection started and ended + an offset of 5 minutes
1186 | if ($evts -eq 'Security') {
1187 | if($TraceEnabled)
1188 | {
1189 | #"create export filter with : "+$RuntimeInMsec
1190 | $expfilter= '' + ''
1191 | }
1192 | else {#only export the last 60 minutes;
1193 | $expfilter= '' + ''
1194 | }
1195 | }
1196 | # Replace slashes in the event filename before building the export paths
1197 | $evtx = [regex]::Replace($evts,"/","-")
1198 | $evttarget = $TraceDir +"\"+ $evtx+".evtx"
1199 | $EventSession = New-Object System.Diagnostics.Eventing.Reader.EventLogSession
1200 | $EventSession.ExportLog($evts,'Logname',$expfilter,$evttarget)
1201 | }
1202 | Pop-Location
1203 | }
1204 |
1205 | function widlogs {
1206 | $widlog="$env:windir\WID\Log"
1207 | $wid = $TraceDir + "\Wid"
1208 | #for the time being we only want to collect the error logs from wid if the cummulative size is less then 25MB was 10 initially
1209 | if ([math]::Round(((Get-ChildItem $widlog -Filter *.log)| Measure-Object -Property Length -sum).sum / 1Mb ,1) -le 25) {
1210 | New-Item -ItemType directory -Path $wid -Force | Out-Null
1211 | foreach ($file in (Get-ChildItem -Path $widlog -Filter *.log) ) {
1212 | Copy-Item ($file.fullname) -Destination $wid
1213 | }
1214 | }
1215 | }
1216 |
1217 | Function GatherTheRest {
1218 | Push-Location $TraceDir
1219 | ForEach ($logfile in $Filescollector) {
1220 | cmd.exe /c $logfile | out-null
1221 | }
1222 | Get-ProxySettings | out-file $env:COMPUTERNAME-ProxySettings.txt
1223 | Get-CertificatesByStore MY| out-file $env:COMPUTERNAME-Certificates-My.txt
1224 | Get-CertificatesByStore Root| out-file $env:COMPUTERNAME-Certificates-Root.txt
1225 | Get-CertificatesByStore CA| out-file $env:COMPUTERNAME-Certificates-CA.txt
1226 | Get-CertificatesByStore NTAuth| out-file $env:COMPUTERNAME-Certificates-NTAuth.txt
1227 | Get-CertificatesByStore ADFSTrustedDevices| out-file $env:COMPUTERNAME-Certificates-ADFSTrustedDevices.txt
1228 |
1229 | if(!$IsProxy) {
1230 | Get-Adfssslcertificate|foreach-object {if($_.CtlStoreName -eq "ClientAuthIssuer" ) {Get-CertificatesByStore ClientAuthIssuer| out-file $env:COMPUTERNAME-Certificates-CliAuthIssuer.txt }}
1231 |
1232 | if ( (Test-IsWID).IsWID) {
1233 | widlogs
1234 | Get-ADFSDBStateFromWID | out-file $env:COMPUTERNAME-ADFS-DatabaseStatus.txt
1235 | }
1236 | }
1237 | else {
1238 | Get-WebApplicationProxySslCertificate|foreach-object { if($_.CtlStoreName -eq "ClientAuthIssuer" ) {Get-ClientAuthIssuerCertificates| out-file $env:COMPUTERNAME-Certificates-CliAuthIssuer.txt} }
1239 | }
1240 |
1241 | Get-DnsClientCache |Sort-Object -Property Entry |format-list |Out-File $env:COMPUTERNAME-DNSClient-Cache.txt
1242 | Get-ChildItem env: |Format-Table Key,Value -Wrap |Out-File $env:COMPUTERNAME-environment-variables.txt
1243 | Get-NetTCPConnection|Sort-Object -Property LocalAddress |out-file $env:COMPUTERNAME-NetTCPConnection.txt
1244 | get-service|Sort-Object -Property Status -Descending |Format-Table DisplayName,Status,StartType -autosize | out-file $env:COMPUTERNAME-services-running.txt
1245 | get-process |Sort-Object Id |Format-Table Name,Id, SessionId,WorkingSet -AutoSize |out-file $env:COMPUTERNAME-tasklist.txt
1246 | Get-Content $env:windir\system32\drivers\etc\hosts |out-file $env:COMPUTERNAME-hosts.txt
1247 | ((get-childitem $env:Windir\adfs\* -include *.dll,*.exe).VersionInfo |Sort-Object -Property FileVersion |Format-Table FileName, FileVersion) |out-file $env:COMPUTERNAME-ADFS-fileversions.txt
1248 | VerifyNetFX |format-list | out-file $env:COMPUTERNAME-DotNetFramework.txt
1249 | Get-WindowsUpdateHTMLReport | out-file $env:COMPUTERNAME-WindowsPatches.html
1250 |
1251 | Pop-Location
1252 | }
1253 |
1254 | function Enable-ADFSPerfcounters {
1255 |
1256 | Param(
1257 | [Parameter(Mandatory=$false)]
1258 | [ValidateSet("ADFSProxy", "ADFSBackend")]
1259 | [string]$Scenario,
1260 |
1261 | [Parameter(Mandatory=$false)]
1262 | [ValidateSet("Create", "Enable", "Disable", "Delete")]
1263 | [string]$Action
1264 |
1265 | )
1266 |
1267 | #sanity checks
1268 | if (-not $PSBoundParameters.ContainsKey('Scenario')) {
1269 | "Missing Parameter. You must supply a Scenario. Allowed values: ADFSProxy, ADFSBackend.";
1270 | break;
1271 | }
1272 |
1273 | if (-not $PSBoundParameters.ContainsKey('Action')) {
1274 | "Missing Parameter. You must supply a Scenario. Allowed values: Create, Enable, Disable, Delete";
1275 | break;
1276 | }
1277 |
1278 | #Action logic
1279 | switch ($Action) {
1280 |
1281 | "Create" { # create the perfcounter collection, we distinguish between WAP and ADFS scenario but always add general counters.
1282 | $joined=""
1283 | foreach ($Counter in $perfcnt.Keys) {
1284 |
1285 | # add role based counters
1286 | if ($perfcnt[$Counter].Type -eq $Scenario) {
1287 | $format = [string]::Format('"{0}" ',$perfcnt[$Counter].CounterName)
1288 | $joined +=$format
1289 | }
1290 |
1291 | #add WID counters if WID is used and we are in ADFSBackend scenario
1292 | if ($perfcnt[$Counter].Type -eq 'WID' -and ( (Test-IsWID).IsWID ) -and ($Scenario -eq 'ADFSBackend')) {
1293 | $format = [string]::Format('"{0}" ',$perfcnt[$Counter].CounterName)
1294 | $joined +=$format
1295 | }
1296 |
1297 | ## always add general perf counters
1298 | if ($perfcnt[$Counter].Type -eq 'General') {
1299 | $format = [string]::Format('"{0}" ',$perfcnt[$Counter].CounterName)
1300 | $joined +=$format
1301 | }
1302 | }
1303 | #build the string and return it
1304 | $result = [String]::Format('Logman.exe create counter {0} -o ".\%COMPUTERNAME%-{0}-perf.blg" -f bincirc -max 512 -v mmddhhmm -c {1} -si 00:00:05',$Scenario,$joined.TrimEnd())
1305 | return $result
1306 | }
1307 | "Enable" { #Enable the perfcounter collection setting it to running state
1308 | return [string]::Format('Logman.exe start {0}',$Scenario)
1309 | }
1310 |
1311 | "Disable" { #Disable the perfcounter collection setting it to stopped state
1312 | return [string]::Format('Logman.exe stop {0}',$Scenario)
1313 | }
1314 |
1315 | "Delete" { #Delete the perfcounter collection
1316 | return [string]::Format('Logman.exe delete {0}',$Scenario)
1317 | }
1318 | }
1319 | }
1320 |
1321 | Function EnablePerfCounter {
1322 | if ($TraceEnabled -and $PerfCounter) {
1323 |
1324 | Write-host "Enabling PerfCounter" -ForegroundColor DarkCyan
1325 |
1326 | if ($IsProxy) { $Scenario='ADFSProxy' } else {$Scenario='ADFSBackend' }
1327 |
1328 | Push-Location $TraceDir
1329 | cmd /c $(Enable-ADFSPerfcounters $Scenario Create) |Out-Null
1330 | cmd /c $(Enable-ADFSPerfcounters $Scenario Enable) |Out-Null
1331 | Pop-Location
1332 | }
1333 | else { Write-Host "Performance Monitoring will not be sampled due to selected scenario" -ForegroundColor DarkCyan }
1334 | }
1335 |
1336 | Function DisablePerfCounter {
1337 | if ($TraceEnabled -and $PerfCounter) {
1338 |
1339 | Write-Host "Stopping Performance Monitoring" -ForegroundColor DarkCyan
1340 |
1341 | if ($IsProxy) { $Scenario='ADFSProxy' } else {$Scenario='ADFSBackend' }
1342 | Push-Location $TraceDir
1343 | cmd /c $(Enable-ADFSPerfcounters $Scenario Disable) |Out-Null
1344 | cmd /c $(Enable-ADFSPerfcounters $Scenario Delete) |Out-Null
1345 | Pop-Location
1346 | }
1347 | else { Write-Host "Performance Monitoring was not sampled due to selected scenario" -ForegroundColor DarkCyan }
1348 | }
1349 |
1350 | Function EnableNetworkTrace {
1351 | if ($TraceEnabled -and $NetTraceEnabled) {
1352 | Write-host "Starting Network Trace" -ForegroundColor DarkCyan
1353 | Push-Location $TraceDir
1354 | #workaround for trace driver initialization failure on certain intel platforms
1355 | $ns = 'netsh trace start capture=yes report=disabled maxsize=1 tracefile=.\%COMPUTERNAME%-network.etl overwrite=yes'
1356 | cmd /c $ns |Out-Null
1357 | cmd /c $DisableNetworkTracer |Out-Null
1358 | #workaround ends
1359 | cmd /c $EnableNetworkTracer |Out-Null
1360 | Pop-Location
1361 | }
1362 | }
1363 |
1364 | Function DisableNetworkTrace {
1365 | if ($TraceEnabled -and $NetTraceEnabled) {
1366 | Write-host "Stopping Network Trace. It may take some time for the data to be flushed to disk. Please be patient`n" -ForegroundColor Yellow
1367 | cmd /c $DisableNetworkTracer |Out-Null
1368 | }
1369 | }
1370 |
1371 | Function EnableLDAPTrace {
1372 | if(!$IsProxy) {
1373 | if ($TraceEnabled -and $LdapTraceEnabled) {
1374 | Write-host "Starting LDAP Trace" -ForegroundColor DarkCyan
1375 | #enable per ldap tracing for: powershell/ise; wsmprovhost and the service itself
1376 | New-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\powershell_ise.exe' -Force | Out-Null
1377 | New-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\powershell.exe' -Force | Out-Null
1378 | New-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\Microsoft.IdentityServer.ServiceHost.exe' -Force | Out-Null
1379 | New-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\wsmprovhost.exe' -Force | Out-Null
1380 |
1381 | Push-Location $TraceDir
1382 | ForEach ($log in $ldapetlOn) {
1383 | cmd.exe /c $log |Out-Null
1384 | }
1385 | Pop-Location
1386 | }
1387 | }
1388 | }
1389 |
1390 | Function DisableLDAPTrace {
1391 | if(!$IsProxy) {
1392 | if($TraceEnabled -and $LdapTraceEnabled) {
1393 | Write-Host "Stopping LDAP Tracing" -ForegroundColor DarkCyan
1394 | Push-Location $TraceDir
1395 |
1396 | ForEach ($log in $ldapetlOff) {
1397 | cmd.exe /c $log |Out-Null
1398 | }
1399 | Pop-Location
1400 | Remove-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\powershell_ise.exe' -Force | Out-Null
1401 | Remove-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\powershell.exe' -Force | Out-Null
1402 | Remove-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\Microsoft.IdentityServer.ServiceHost.exe' -Force | Out-Null
1403 | Remove-Item 'HKLM:\System\CurrentControlSet\Services\ldap\Tracing\wsmprovhost.exe' -Force | Out-Null
1404 | }
1405 | else { Write-host "LDAP Tracing was not enabled" -ForegroundColor DarkCyan }
1406 | }
1407 | }
1408 |
1409 | Function EnableWAPTrace {
1410 | if($IsProxy) {
1411 | if ($TraceEnabled -and $WAPTraceEnabled) {
1412 | Write-host "Starting WAP Tracing" -ForegroundColor DarkCyan
1413 | Push-Location $TraceDir
1414 | ForEach ($log in $WAPTraceOn) {
1415 | cmd.exe /c $log |Out-Null
1416 | }
1417 |
1418 | Pop-Location
1419 | }
1420 | }
1421 | }
1422 |
1423 | Function DisableWAPTrace {
1424 | if($IsProxy) {
1425 | if($TraceEnabled -and $WAPTraceEnabled) {
1426 | Write-Host "Stopping WAP Tracing" -ForegroundColor DarkCyan
1427 | Push-Location $TraceDir
1428 |
1429 | ForEach ($log in $WapTraceOff) {
1430 | cmd.exe /c $log |Out-Null
1431 | }
1432 |
1433 | Pop-Location
1434 | }
1435 | else { Write-host "WAP Tracing was not enabled" -ForegroundColor DarkCyan }
1436 | }
1437 | }
1438 |
1439 | function Test-KRBEncTypePolicy {
1440 | # Specify the registry key path and the value name
1441 | $keyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters\"
1442 | $valueName = "SupportedEncryptionTypes"
1443 | #by default all Enctypes are enabled .This may change in future we assume defaults unless a policy is configured
1444 | $EncType = 31
1445 |
1446 | $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($keyPath)
1447 |
1448 | #if policy key exists try to get the value
1449 | if (($key.Name)) {
1450 | if ($key.GetValueNames() -icontains $valueName) {
1451 | try {
1452 | [int]$EncType= $key.GetValue($valueName);
1453 | #close key handle when done
1454 | $key.Close()
1455 | }
1456 | catch {
1457 | #regvalue is not defined only thing we want to do is to close key handle
1458 | $key.Close()
1459 | }
1460 | }
1461 | }
1462 |
1463 | #do we still need to look into the classic path HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters ?
1464 | #if yes we will loop it in here.
1465 |
1466 | #desktop GPO allows to configure ETypes only and FutureFlags. if the value is greater than 0x1f in GPO we must expect that Futureflags had been set
1467 | #remove the futureflag from enumeration
1468 |
1469 | if ($EncType -gt 31) {
1470 | $EncType = $EncType - 2147483616
1471 | }
1472 | #watch out if there is someone configuring the regkeys manually instead via GPO, it might be they use wrong or negative values
1473 | # finally convert the value to a meaningful string and return
1474 | return ([KrbEnum]::EnumerateKrb($EncType))
1475 | }
1476 |
1477 | Function Test-ADFSComputerNameEqFarmName {
1478 | param(
1479 | [Parameter(Mandatory=$true)]
1480 | [string]$farmName
1481 | )
1482 |
1483 | $errortmpl=@"
1484 | Error: The host computer name '{0}' is identical to the configured ADFS Farmname '{1}'.
1485 | This configuration is unsupported and is known to cause the following issues:
1486 | - windows integrated authentication will fail since the kerberos SPN cannot be registered on the service account without causing conflicts
1487 | - failure to perform Remote Management (WinRM)
1488 | - cause WID synchronization issues
1489 | - preventing the setup/use of additional farmnodes
1490 | "@
1491 | try
1492 | {
1493 | $netprop = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
1494 | $computerName = [string]::Format("{0}.{1}",$netprop.HostName,$netprop.Domainname)
1495 |
1496 | if ($computerName -eq $farmName)
1497 | {
1498 | $testResult = [string]::Format($errortmpl,$computerName.ToUpper(),$farmName.ToUpper())
1499 | return $testResult
1500 | }
1501 |
1502 | $testResult = "Test passed"
1503 | return $testResult
1504 | }
1505 | catch [Exception]
1506 | {
1507 | return [string]::format("Error: Failed to verify the computer name and ADFS Farmname are not overlapping. Error {0}", $_.Exception.Message)
1508 | }
1509 | }
1510 |
1511 | Function Test-ADFSFarmnameIsNotCNAME {
1512 | param(
1513 | [Parameter(Mandatory=$true)]
1514 | [string]$farmName
1515 | )
1516 | try {
1517 | $resolutionResult = [System.Net.Dns]::GetHostEntry($farmname)
1518 | $resolvedHostName = $resolutionResult.HostName
1519 |
1520 | if ($resolvedHostName -ne $farmname) {
1521 | $testResult = [String]::format("Warning: The ADFS Farm Name '{0}' is resolved as host '{1}'. This might break windows integrated authentication scenarios.`n",$farmname,$resolvedHostName)
1522 | return $testResult
1523 | }
1524 |
1525 | $testResult = "Test passed"
1526 | return $testResult
1527 | } catch [System.Net.Sockets.SocketException] {
1528 | return [string]::format("Error: Could not resolve the farm name {0} with exception '{1}'",$farmname, $_.Exception.Message)
1529 | }
1530 | }
1531 |
1532 | function Get-ADFSFarmNameFromSSLBinding {
1533 | <#
1534 | .SYNOPSIS
1535 | Retrieves the ADFS farm name from SSL certificate bindings.
1536 |
1537 | .DESCRIPTION
1538 | This function examines SSL certificate bindings to identify the ADFS farm hostname.
1539 | It filters out localhost, enterprise registration, and certificate authentication endpoints
1540 | to find the primary ADFS service hostname.
1541 |
1542 | .OUTPUTS
1543 | String - Returns the first valid ADFS farm hostname found, or $null if none found.
1544 | #>
1545 |
1546 | try {
1547 | # Get all SSL certificate bindings first
1548 | $sslCertificates = Get-AdfsSslCertificate
1549 | # Find the first valid hostname
1550 | foreach ($cert in $sslCertificates) {
1551 | if (($cert.PortNumber -eq 443) -and
1552 | ($cert.AppId -eq '5d89a20c-beab-4389-9447-324788eb944a') -and
1553 | ($cert.HostName -inotlike 'localhost') -and
1554 | ($cert.HostName -inotlike 'enterpriseregistration*') -and
1555 | ($cert.HostName -inotlike 'certauth*')) {
1556 |
1557 | # Return the first valid hostname immediately
1558 | return $cert.HostName
1559 | }
1560 | }
1561 | # If no valid hostname found, return $null
1562 | return $null
1563 | }
1564 | catch {
1565 | Write-Warning "Failed to retrieve ADFS SSL certificate bindings: $($_.Exception.Message)"
1566 | return $null
1567 | }
1568 | }
1569 |
1570 | function Get-ServiceAccountDetails {
1571 | #initialize object to store the result: gsad is the accronym of the function name ( g = get, sa = service account, d = details )
1572 | $gsad = New-Object -TypeName PSObject
1573 |
1574 | #only execute if we are not on proxy/wap
1575 | if (!$IsProxy) {
1576 | #get currently config service account if this fails
1577 | try {
1578 | $svc = new-object System.ServiceProcess.ServiceController('adfssrv')
1579 | $SVCACC = [ServiceConfigHelper]::GetServiceAccount($svc.Name)
1580 | } catch {
1581 | $gsad | Add-Member -MemberType NoteProperty -Name "ADFS Service Account" -Value "Error: Failed to retrieve ADFS Service Account from Service Controll Manager. The AD FS Role may not be installed. Skipping Service Account checks."
1582 | return $gsad
1583 | }
1584 |
1585 | if (!$SVCACC) {
1586 | #this would be unexpected as the service account is mandatory for a service to run unless someone deleted the service account from service config/registry
1587 | $gsad | Add-Member -MemberType NoteProperty -Name "ADFS Service Account" -Value "Error: No ADFS Service Account configured. This is unexpected and could mean the service account was removed from the service configuration in Windows(Registry)."
1588 | return $gsad
1589 | } else {
1590 | $gsad | Add-Member -MemberType NoteProperty -Name "ADFS Service Account" -Value $SVCACC
1591 | }
1592 |
1593 | #detect name format UPN vs Legacy
1594 | if ($SVCACC.contains('@')) {
1595 | $filter ="(userprincipalname="+$SVCACC+")"
1596 | $domain = $SVCACC.Split('@')[1]
1597 | }
1598 |
1599 | if ($SVCACC.contains('\')) {
1600 | $filter ="(samaccountname="+$SVCACC.Split('\')[1]+")"
1601 | $domain = $SVCACC.Split('\')[0]
1602 | }
1603 |
1604 | $conn= (New-Object System.DirectoryServices.DirectoryEntry("LDAP://$domain/RootDSE")).dnshostname
1605 | [string]$att = "*"
1606 |
1607 | #Performing LDAP Lookup of ADFS Service Account
1608 | $re= LDAPQuery -filter $filter -att $att -conn $conn
1609 | #1st test if its a GMSA
1610 | $gmsa =$false
1611 |
1612 | if(($re.GetType().Name -eq 'SearchResponse') -and ($re.Entries.Count -eq 1)) {
1613 | $gmsa = [Bool]($re.Entries.Attributes.objectclass.GetValues('string') -eq 'msDS-GroupManagedServiceAccount')
1614 | $gsad | Add-Member -MemberType NoteProperty -Name "IsManagedServiceAccount" -Value $gmsa
1615 |
1616 | if($gmsa -eq $true) {
1617 | $adl = new-object System.DirectoryServices.ActiveDirectorySecurity
1618 | $adl.SetSecurityDescriptorBinaryForm($re.Entries[0].Attributes.'msds-groupmsamembership'[0])
1619 | $gsad | Add-Member -MemberType NoteProperty -Name "GMSA allowed Hosts" -value ($adl.AccessToString)
1620 | }
1621 | #try reading the SPN configuration from the service account
1622 | try {
1623 | $gsad | Add-Member -MemberType NoteProperty -Name "OnAccountRegisteredSPN" -Value ($re.Entries.Attributes.serviceprincipalname.GetValues('string'))
1624 | } catch {
1625 | #we failed to read the SPN value and must assume there is no SPN configured for this service account
1626 | if ($_.FullyQualifiedErrorID -eq 'InvokeMethodOnNull' ) {
1627 | $gsad | Add-Member -MemberType NoteProperty -Name "OnAccountRegisteredSPN" -Value "ERROR: No SPNs are configured for this Service Account."
1628 | }
1629 | }
1630 |
1631 | #whilst we are at it try and get the kerberos encryption type value if there is one configured
1632 | Try {
1633 | $EncType= [int]::Parse($re.Entries[0].Attributes.'msds-supportedencryptiontypes'.GetValues('string'))
1634 | } Catch {
1635 | #if we dont find a configured value we must assume its not set so lets use -1 explicitly
1636 | $EncType=-1
1637 | }
1638 |
1639 | } else {
1640 | if ($re.Response.ResultCode -eq "NoSuchObject") {
1641 | $gsad | Add-Member -MemberType NoteProperty -Name "ServiceAccount query failed" -value "Service Account not found. Ldap Error:`r`n$($re.Response.ErrorMessage)"
1642 | } else {
1643 | $gsad | Add-Member -MemberType NoteProperty -Name "ServiceAccount query failed" -value "Unable to resolve the Service Account. Use AD tools like 'setspn' or 'dsa.msc' to verify the Account exists in AD."
1644 | }
1645 | # if we failed to find the account use -2 so we later dont call enum but log appropropriate message
1646 | $EncType=-2
1647 | }
1648 |
1649 | #refined the Dupe SPN check to not only rely on get-ADFSProperties alone for building the SPN query...this may not work in all cases
1650 | #we now go by the order: http hostname binding -> ADFS Properties -> lastly directly from database
1651 | $farmname = Get-ADFSFarmNameFromSSLBinding
1652 |
1653 | #hostname may still be empty so we may have failed to find the bindings.
1654 | #let assume ADFS service is running and we can query adfsproperties from powershell
1655 | if ($null -eq $farmname ) {
1656 | try {
1657 | $farmname = (get-adfsproperties).hostname
1658 | } catch { }
1659 | }
1660 |
1661 | # if still no hostname last attempt to get the farmname from DB
1662 | # this is best effort here and limited to WID. The user may not be a DBA or have access to the SQL server
1663 | if ($null -eq $farmname ) {
1664 | try {
1665 | $dbconfig = Test-IsWID
1666 | if ($dbconfig.IsWID -and ($dbconfig.IsWIDStarted -ne [System.ServiceProcess.ServiceControllerStatus]::Running )) {
1667 | $errMsg = "Error: WID (Windows Internal Database) service is not running."
1668 | throw [System.Exception]::new($errMsg)
1669 | }
1670 | if ($null -ne $dbconfig.ConfigurationDatabaseConnectionString) {
1671 | $farmname = (get-servicesettingsfromdb -DBConnectionString $dbconfig.ConfigurationDatabaseConnectionString ).ServiceSettingsData.SecurityTokenService.Host.Name
1672 | }
1673 | } catch {}
1674 | }
1675 |
1676 | #if we have a hostname lets attempt to perform a check for duplicate SPNs
1677 | #first check create the connection object. Use GlobalCatalog as we may have a dupe in a child domain of the forest
1678 | if (!($null -eq $farmname )) {
1679 | $gconn= (New-Object System.DirectoryServices.DirectoryEntry("GC://$domain/RootDSE")).dnshostname
1680 | $filter= [string]::format("(serviceprincipalname=*/{0})", $farmname )
1681 | [string]$att = "*"
1682 | }
1683 |
1684 | #if we dont have a hostname we didnt cannot create the ldap connection and filter so we dont need to run the query after all
1685 | if (!($null -eq $gconn)) {
1686 |
1687 | $re= LDAPQuery -filter $filter -att $att -conn $gconn
1688 |
1689 | if ($re.GetType().Name -eq 'SearchResponse' -And ($re.entries.count -ge 1)) {
1690 | $obj= $re.Entries | ForEach-Object {
1691 | [String]::Format("`r`nAccount: {0}`r`nSPNs : {1}`n",
1692 | $_.distinguishedName ,
1693 | ($_.Attributes.'serviceprincipalname'.GetValues('string') -join " ; ") )
1694 | }
1695 | $gsad | Add-Member -MemberType NoteProperty -Name "Duplicate SPN" -Value $obj
1696 | } else {
1697 | $gsad | Add-Member -MemberType NoteProperty -Name "Duplicate SPN" -Value "Warning: Duplicate SPN check failed.`r`nThe query may have timed-out or may have returned no results.`r`nYou can use 'setspn.exe -f -q */$($farmname)' to query for duplicate SPN's in the forest.`r`n."
1698 | }
1699 | }
1700 |
1701 | #test for DNS Cname since it can break kerberos auth
1702 | if (!($null -eq $farmname )) {
1703 | $adfscnamecheck = Test-ADFSFarmnameIsNotCNAME -farmName $farmname
1704 | if ($adfscnamecheck -ne "Test passed") {
1705 | $gsad | Add-Member -MemberType NoteProperty -Name "DNS-Alias Check" -Value $adfscnamecheck
1706 | } else {
1707 | $gsad | Add-Member -MemberType NoteProperty -Name "DNS-Alias Check" -Value ( [String]::Format("Success: The ADFS Farmname '{0}' resolves correctly without CNAME indirection.", $farmname) )
1708 | }
1709 | } else {
1710 | $gsad | Add-Member -MemberType NoteProperty -Name "DNS-Alias Check" -Value "Test skipped. Could not retrieve ADFS farmname from configuration. The service may not be running or is not yet configured"
1711 | }
1712 |
1713 | #computername must not be identical to farmname else breaks kerberos auth and farm management
1714 | if (!($null -eq $farmname )) {
1715 | $adfseqhostres = Test-ADFSComputerNameEqFarmName -farmName $farmname
1716 | if ($adfseqhostres -ne "Test passed") {
1717 | $gsad | Add-Member -MemberType NoteProperty -Name "Farmname-Computername Check" -Value $adfseqhostres
1718 | } else {
1719 | $gsad | Add-Member -MemberType NoteProperty -Name "Farmname-Computername Check" -Value "Success: ADFS Farmname and Computername are different."
1720 | }
1721 | } else {
1722 | $gsad | Add-Member -MemberType NoteProperty -Name "Farmname-Computername Check" -Value "Test skipped. Could not retrieve ADFS farmname from configuration. The service may not be running or is not yet configured"
1723 | }
1724 |
1725 | #Finally validate that Kerberos Etype Config is sound and we have a matching config between OS and Service Account
1726 | #some message strings
1727 | $RC4NotSetMsg="The Service Account is not configured for AES support. Service tickets will be RC4 encrypted!
1728 | `r`nWe recommend configuring the ADFS Service Account for AES Support.`r`nIn Active Directory configure the attribute 'msds-supportedencryptiontypes' for the ADFS ServiceAccount with a value of:`r`n24(decimal) => AES only `n or `n28(decimal) => AES & RC4"
1729 |
1730 | $RC4NoPolicysupMsg="The ADFS service account is not configured properly. Local policy/registry for KerberosEncryptionTypes disabled RC4 support,`r`nbut the service account has not been configured for AES support.
1731 | `r`nThis configuration can lead to authentication failures and other erroneous behavior and MUST be corrected.
1732 | `r`nWe recommend configuring the ADFS Service Account for AES Support.`r`nIn Active Directory configure the attribute 'msds-supportedencryptiontypes' for the ADFS ServiceAccount with a value of:`r`n24(decimal) => AES only `n or `n28(decimal) => AES & RC4"
1733 |
1734 | #get KrbConfig from OS Policy
1735 | $HostKrbCfg = Test-KRBEncTypePolicy
1736 |
1737 | #theoretically this cannot be null unless the module failed
1738 | if (!($null -eq $HostKrbCfg)) {
1739 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEtype from OS (Policy)" -Value $($HostKrbCfg -join " | ")
1740 | }
1741 |
1742 | #check etypes from ServiceAccountQuery if not set or explicitly 0 default to RC4
1743 | #if we failed to find service account previously..skip etype config evaluation
1744 |
1745 | switch ($EncType) {
1746 | 0 { $SvcKrbCfg = "RC4_HMAC";
1747 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEtype from ServiceAccount" -Value "explicitly set to 0. Defaulting to RC4_HMAC"
1748 | }
1749 | -1 { $SvcKrbCfg = "RC4_HMAC";
1750 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEType from ServiceAccount" -Value "not configured. Defaulting to RC4_HMAC"
1751 | }
1752 | -2 { #we failed to find service account previously..nothing to evaluate here
1753 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEType from ServiceAccount" -Value "Failed to enumerate ServiceAccount"
1754 | }
1755 | default { $SvcKrbCfg = [KrbEnum]::EnumerateKrb($EncType)
1756 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEType from ServiceAccount" -Value $($SvcKrbCfg -join " | ")
1757 | }
1758 | }
1759 |
1760 | if (!($null -eq $HostKrbCfg) -and !($null -eq $SvcKrbCfg)) {
1761 | $commonItems = [System.Linq.Enumerable]::Intersect(
1762 | [System.Collections.Generic.List[object]]@($SvcKrbCfg),
1763 | [System.Collections.Generic.List[object]]@($HostKrbCfg)
1764 | )
1765 | }
1766 |
1767 | #we have intersection and AES is listed --> AES is used
1768 | if ($commonItems.Count -gt 0 -and $commonItems -like "*AES*") {
1769 | $gsad | Add-Member -MemberType NoteProperty -Name "Kerberos EncryptionType expected" -Value "AES"
1770 |
1771 | #we have intersection but not AES but RC4 or maybe even weaker ..log warning and recommend AES
1772 | } elseif ( $commonItems.Count -gt 0 -and ($commonItems -notlike "*AES*") ) {
1773 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEType expected" -Value "RC4"
1774 | $gsad | Add-Member -MemberType NoteProperty -Name "Warning" -Value $RC4NotSetMsg
1775 |
1776 | #no intersection at all. This does not look good and auth will break . log error
1777 | } elseif ( $null -eq $commonItems.Count -and ($SvcKrbCfg -like "*RC4*") -and ($HostKrbCfg -notlike "*RC4*") ) {
1778 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEType expected" -Value "RC4"
1779 | $gsad | Add-Member -MemberType NoteProperty -Name "Error" -Value $RC4NoPolicysupMsg
1780 |
1781 | #we should only get here if we have no Service Account queried (notFound/NotExisting)
1782 | } else {
1783 | $gsad | Add-Member -MemberType NoteProperty -Name "KrbEType expected" -Value "Cannot predict KrbEType usage. A previous query failed"
1784 |
1785 | }
1786 | }
1787 |
1788 | Return $gsad
1789 | }
1790 |
1791 | Function GetADFSConfig {
1792 |
1793 | Push-Location $TraceDir
1794 | if ($IsProxy) { # ADFS proxy 2012
1795 | if ($WinVer -eq [Version]"6.2.9200" ) {
1796 | Get-AdfsProxyProperties | format-list * | Out-file "Get-AdfsProxyProperties.txt"
1797 | }
1798 | else { # ADFS 2012 R2 or ADFS 2016 or 2019
1799 | Get-WebApplicationProxyApplication | format-list * | Out-file "Get-WebApplicationProxyApplication.txt"
1800 | Get-WebApplicationProxyAvailableADFSRelyingParty | format-list * | Out-file "Get-WebApplicationProxyAvailableADFSRelyingParty.txt"
1801 | Get-WebApplicationProxyConfiguration | format-list * | Out-file "Get-WebApplicationProxyConfiguration.txt"
1802 | Get-WebApplicationProxyHealth | format-list * | Out-file "Get-WebApplicationProxyHealth.txt"
1803 | Get-WebApplicationProxySslCertificate | format-list * | Out-file "Get-WebApplicationProxySslCertificate.txt"
1804 | copy-item -path "$env:windir\ADFS\Config\Microsoft.IdentityServer.ProxyService.exe.config" -Destination $TraceDir
1805 | }
1806 | }
1807 | else {
1808 | # Common ADFS commands to all version
1809 | if ((Get-AdfsSyncProperties).Role -eq 'PrimaryComputer') {
1810 | Get-AdfsAttributeStore | format-list * | Out-file "Get-AdfsAttributeStore.txt"
1811 | Get-AdfsCertificate | format-list * | Out-file "Get-AdfsCertificate.txt"
1812 | Get-AdfsClaimDescription | format-list * | Out-file "Get-AdfsClaimDescription.txt"
1813 | Get-AdfsClaimsProviderTrust | format-list * | Out-file "Get-AdfsClaimsProviderTrust.txt"
1814 | Get-AdfsEndpoint | format-list * | Out-file "Get-AdfsEndpoint.txt"
1815 | Get-AdfsProperties | format-list * | Out-file "Get-AdfsProperties.txt"
1816 | Test-WiaSupportedUseragents | format-list * | Out-file "Get-ADFSproperties.txt" -Append
1817 | Get-AdfsRelyingPartyTrust | format-list * | Out-file "Get-AdfsRelyingPartyTrust.txt"
1818 | }
1819 |
1820 | Get-AdfsSyncProperties | format-list * | Out-file "Get-AdfsSyncProperties.txt"
1821 | Get-AdfsSslCertificate | format-list * | Out-file "Get-AdfsSslCertificate.txt"
1822 | Get-ServiceAccountDetails | format-list * | Out-file "Get-ServiceAccountDetails.txt"
1823 |
1824 | if ($WinVer -ge [Version]"10.0.14393") {# ADFS commands specific to ADFS 2016,2019,2022
1825 | if((Get-AdfsSyncProperties).Role -eq 'PrimaryComputer') {
1826 | Get-AdfsAccessControlPolicy | format-list * | Out-file "Get-AdfsAccessControlPolicy.txt"
1827 | Get-AdfsApplicationGroup | format-list * | Out-file "Get-AdfsApplicationGroup.txt"
1828 | Get-AdfsApplicationPermission | format-list * | Out-file "Get-AdfsApplicationPermission.txt"
1829 | Get-AdfsCertificateAuthority | format-list * | Out-file "Get-AdfsCertificateAuthority.txt"
1830 | Get-AdfsClaimsProviderTrustsGroup | format-list * | Out-file "Get-AdfsClaimsProviderTrustsGroup.txt"
1831 | Get-AdfsFarmInformation | format-list * | Out-file "Get-AdfsFarmInformation.txt"
1832 | Get-AdfsLocalClaimsProviderTrust | format-list * | Out-file "Get-AdfsLocalClaimsProviderTrust.txt"
1833 | Get-AdfsNativeClientApplication | format-list * | Out-file "Get-AdfsNativeClientApplication.txt"
1834 | Get-AdfsRegistrationHosts | format-list * | Out-file "Get-AdfsRegistrationHosts.txt"
1835 | Get-AdfsRelyingPartyTrustsGroup | format-list * | Out-file "Get-AdfsRelyingPartyTrustsGroup.txt"
1836 | Get-AdfsScopeDescription | format-list * | Out-file "Get-AdfsScopeDescription.txt"
1837 | Get-AdfsServerApplication | format-list * | Out-file "Get-AdfsServerApplication.txt"
1838 | Get-AdfsTrustedFederationPartner | format-list * | Out-file "Get-AdfsTrustedFederationPartner.txt"
1839 | Get-AdfsWebApiApplication | format-list * | Out-file "Get-AdfsWebApiApplication.txt"
1840 | Get-AdfsAdditionalAuthenticationRule | format-list * | Out-file "Get-AdfsAdditionalAuthenticationRule.txt"
1841 | Get-AdfsAuthenticationProvider | format-list * | Out-file "Get-AdfsAuthenticationProvider.txt"
1842 | Get-AdfsAuthenticationProviderWebContent | format-list * | Out-file "Get-AdfsAuthenticationProviderWebContent.txt"
1843 | Get-AdfsClient | format-list * | Out-file "Get-AdfsClient.txt"
1844 | Get-AdfsGlobalAuthenticationPolicy | format-list * | Out-file "Get-AdfsGlobalAuthenticationPolicy.txt"
1845 | Get-AdfsGlobalWebContent | format-list * | Out-file "Get-AdfsGlobalWebContent.txt"
1846 | Get-AdfsNonClaimsAwareRelyingPartyTrust | format-list * | Out-file "Get-AdfsNonClaimsAwareRelyingPartyTrust.txt"
1847 | Get-AdfsRelyingPartyWebContent | format-list * | Out-file "Get-AdfsRelyingPartyWebContent.txt"
1848 | Get-AdfsWebApplicationProxyRelyingPartyTrust | format-list * | Out-file "Get-AdfsWebApplicationProxyRelyingPartyTrust.txt"
1849 | Get-AdfsWebConfig | format-list * | Out-file "Get-AdfsWebConfig.txt"
1850 | Get-AdfsWebTheme | format-list * | Out-file "Get-AdfsWebTheme.txt"
1851 | Get-AdfsRelyingPartyWebTheme | format-list * | Out-file "Get-AdfsRelyingPartyWebTheme.txt"
1852 | }
1853 | copy-item -path "$env:windir\ADFS\Microsoft.IdentityServer.ServiceHost.Exe.Config" -Destination $TraceDir
1854 | Get-ADFSAzureMfaAdapterconfig |format-list | Out-file "Get-ADFSAzureMfaAdapterconfig.txt"
1855 |
1856 | ##comming soon: WHFB Cert Trust Informations
1857 |
1858 | if ($WinVer -ge [Version]"10.0.17763") { #ADFS command specific to ADFS 2019+
1859 | if((Get-AdfsSyncProperties).Role -eq 'PrimaryComputer') {
1860 | Get-AdfsDirectoryProperties | format-list * | Out-file "Get-AdfsDirectoryProperties.txt"
1861 | }
1862 | }
1863 |
1864 | }
1865 |
1866 | if ($WinVer -eq [Version]"6.3.9600") { # ADFS commands specific to ADFS 2012 R2/consolidate this in next release
1867 | Get-AdfsAdditionalAuthenticationRule | format-list * | Out-file "Get-AdfsAdditionalAuthenticationRule.txt"
1868 | Get-AdfsAuthenticationProvider | format-list * | Out-file "Get-AdfsAuthenticationProvider.txt"
1869 | Get-AdfsAuthenticationProviderWebContent | format-list * | Out-file "Get-AdfsAuthenticationProviderWebContent.txt"
1870 | Get-AdfsClient | format-list * | Out-file "Get-AdfsClient.txt"
1871 | Get-AdfsGlobalAuthenticationPolicy | format-list * | Out-file "Get-AdfsGlobalAuthenticationPolicy.txt"
1872 | Get-AdfsGlobalWebContent | format-list * | Out-file "Get-AdfsGlobalWebContent.txt"
1873 | Get-AdfsNonClaimsAwareRelyingPartyTrust | format-list * | Out-file "Get-AdfsNonClaimsAwareRelyingPartyTrust.txt"
1874 | Get-AdfsRelyingPartyWebContent | format-list * | Out-file "Get-AdfsRelyingPartyWebContent.txt"
1875 | Get-AdfsWebApplicationProxyRelyingPartyTrust | format-list * | Out-file "Get-AdfsWebApplicationProxyRelyingPartyTrust.txt"
1876 | Get-AdfsWebConfig | format-list * | Out-file "Get-AdfsWebConfig.txt"
1877 | Get-AdfsWebTheme | format-list * | Out-file "Get-AdfsWebTheme.txt"
1878 | copy-item -path "$env:windir\ADFS\Microsoft.IdentityServer.ServiceHost.Exe.Config" -Destination $TraceDir
1879 | }
1880 | elseif ($WinVer -eq [Version]"6.2.9200") { # No specific cmdlets for this version
1881 | }
1882 | }
1883 | Pop-Location
1884 | }
1885 |
1886 | Function EndOfCollection {
1887 | $date = get-date -Format yyyy-dd-MM_hh-mm
1888 | $computername = (Get-Childitem env:computername).value
1889 | $zip = $computername + "_ADFS_traces_"+$date
1890 | $datafile = "$(Join-Path -Path $path -ChildPath $zip).zip"
1891 | Stop-Transcript |Out-Null
1892 | Write-host "Creating Archive File" -ForegroundColor Green
1893 |
1894 | [System.IO.Compression.ZipFile]::CreateFromDirectory($TraceDir, $datafile)
1895 |
1896 | Write-host "Archive File created in $datafile" -ForegroundColor Green
1897 |
1898 | # Cleanup the Temporary Folder (if error retain the temp files)
1899 | if(Test-Path -Path $Path) {
1900 | Write-host "Removing Temporary Files" -ForegroundColor Green
1901 | Remove-Item -Path $TraceDir -Force -Recurse | Out-Null
1902 | }
1903 | else {
1904 | Write-host "The Archive could not be created. Keeping Temporary Folder $TraceDir" -ForegroundColor Yellow
1905 | New-Item -ItemType directory -Path $Path -Force | Out-Null
1906 | }
1907 | }
1908 |
1909 | Function GetDRSConfig {
1910 | if ((-Not $IsProxy) -And ($WinVer -gt [Version]"6.2.9200")) {
1911 | Push-Location $TraceDir
1912 | Get-AdfsDeviceRegistrationUpnSuffix | format-list * | Out-file "Get-AdfsDeviceRegistrationUpnSuffix.txt"
1913 | Try { $drs= Get-AdfsDeviceRegistration; $drs| Out-file "Get-AdfsDeviceRegistration.txt" } Catch { $_.Exception.Message | Out-file "Get-AdfsDeviceRegistration.txt" }
1914 |
1915 | $dse = (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+(Get-WmiObject -Class Win32_ComputerSystem).Domain+"/RootDSE"))
1916 | $conn= $dse.dnsHostName
1917 | $basednq = "CN=DeviceRegistrationService,CN=Device Registration Services,CN=Device Registration Configuration,CN=Services," +$dse.configurationNamingContext
1918 | $filter= "(objectClass=*)"
1919 | $re= LDAPQuery -filter $filter -att $att -conn $conn -basedn $basednq
1920 | if($re.GetType().Name -eq 'SearchResponse') {
1921 | $DScloudissuerpubliccert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
1922 | $DSissuerpubliccert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
1923 | try{$DScloudissuerpubliccert.Import($re.Entries.Attributes.'msds-cloudissuerpubliccertificates'.GetValues('byte[]')[0])}catch{}
1924 | try{$DSissuerpubliccert.Import($re.Entries.Attributes.'msds-issuerpubliccertificates'.GetValues('byte[]')[0]) }catch{}
1925 |
1926 | "DRS Cloud Issuer Certificate`nThumbprint:"+ $DScloudissuerpubliccert.Thumbprint + "`nIssuer:" +$DScloudissuerpubliccert.Issuer |Out-File Get-AdfsDeviceRegistration.txt -Append
1927 | "`nDRS Onprem Issuer Certificate`nThumbprint:"+ $DSissuerpubliccert.Thumbprint + "`nIssuer:" +$DSissuerpubliccert.Issuer |Out-File Get-AdfsDeviceRegistration.txt -Append
1928 | }
1929 | else { "DRS Service Object search failed: "+$re.Message |Out-File Get-AdfsDeviceRegistration.txt -Append }
1930 |
1931 | pop-location
1932 | }
1933 | }
1934 |
1935 | function netfxversion {
1936 | $fx=[PSCustomObject]@{};
1937 | $fx| Add-Member -MemberType NoteProperty -Name 'Release' -Value ((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release)
1938 | foreach ($fxver in ($fxversions.GetEnumerator() | Sort-Object -Property Value )) {
1939 | if (($fx.Release -eq $fxver.Value) -ne 0) {
1940 | $fx | Add-Member -MemberType NoteProperty -Name 'Version' -Value ($fxver.Key.ToString())
1941 | }
1942 | }
1943 |
1944 | if ($fx.Release -ilt [int]394802) {
1945 | $fx | Add-Member -MemberType NoteProperty -Name 'Lifecyclestate' -Value 'no longer supported. Please Update to at minimum .Net 4.6.2'
1946 | }
1947 | else { $fx | Add-Member -MemberType NoteProperty -Name 'Lifecyclestate' -Value 'supported' }
1948 | return $fx
1949 | }
1950 |
1951 | function VerifyNetFX {
1952 | $nfx = [PSCustomObject]@{}
1953 | $cSP = [Net.ServicePointManager]::SecurityProtocol
1954 | $SUSC= switch ((get-itemproperty -PATH "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319").SchUseStrongCrypto)
1955 | { $null {"not configured"} 0 {" explicitly disabled by registry value (0)"} 1 {"explicitly enabled by registry"} }
1956 | $SDTV= switch ((get-itemproperty -PATH "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319").SystemDefaultTlsVersions)
1957 | { $null {"not configured"} 0 {" explicitly disabled by registry value (0)"} 1 {"explicitly enabled by registry"} }
1958 |
1959 | $fxr = netfxversion
1960 |
1961 | $nfx | Add-Member -MemberType NoteProperty -Name '.Net-Release' -Value ([String]::Format([CultureInfo]::InvariantCulture, "{0} {1} is {2}.", $fxr.Release,$fxr.Version,$fxr.Lifecyclestate))
1962 | if (($cSP -split ', ' ) -contains 'TLS12' -or ($cSP -split ', ' ) -contains 'SystemDefault') {
1963 | $nfx | Add-Member -MemberType NoteProperty -Name 'ServicePoint' -Value ("SSL/TLS Protocols available: " + $cSP)
1964 | $nfx | Add-Member -MemberType NoteProperty -Name 'Information' -Value ("The Script detected that StrongCrypto is enabled`neither by default (2019 or higher) or by Registry")
1965 | $nfx | Add-Member -MemberType NoteProperty -Name 'SchUseStrongCrypto' -Value $SUSC
1966 | $nfx | Add-Member -MemberType NoteProperty -Name 'SystemDefaultTlsVersions' -Value $SDTV
1967 | }
1968 | else {
1969 | $nfx | Add-Member -MemberType NoteProperty -Name 'ServicePoint' -Value ("SSL/TLS Protocols available: " + $cSP)
1970 | $nfx | Add-Member -MemberType NoteProperty -Name 'Critical' -Value ("Current Configuration implies TLS1.2 is NOT enabled for .Net Framework`n")
1971 | $nfx | Add-Member -MemberType NoteProperty -Name 'SchUseStrongCrypto' -Value $SUSC
1972 | $nfx | Add-Member -MemberType NoteProperty -Name 'SystemDefaultTlsVersions' -Value $SDTV
1973 | }
1974 | return $nfx
1975 | }
1976 |
1977 | function Get-NetframeworkInstalledUpdates {
1978 | $updates = New-Object -ComObject "Microsoft.Update.Session"
1979 | $searcher = $updates.CreateUpdateSearcher()
1980 | $historyCount = $searcher.GetTotalHistoryCount()
1981 | $updateHistory = $searcher.QueryHistory(0,$historyCount )
1982 |
1983 | # This will return a list of all updates installed over the existence of this system including Defender, Malicious Software Removal Tool etc..
1984 | # We only care about Framework Cumulative Updates here (the last 5x) that got successfully installed
1985 | try {
1986 | $updatelist = ($updateHistory | Where-Object { ($_.Title.Contains('Cumulative Update for .NET Framework')) -and $_.ResultCode -eq 2})[0..4] |
1987 | Select-Object -Property @{Name='Installation time'; Expression={$_.Date}},
1988 | @{Name='Title (KB)'; Expression={$_.Title}},
1989 | @{Name='KB Number'; Expression={
1990 | [regex]::replace($_.Title, '.*\(KB(\d+)\).*', 'KB$1')
1991 | } },
1992 | @{Name='Support Url'; Expression={
1993 | [regex]::replace($_.Title, '.*\(KB(\d+)\).*', 'https://support.microsoft.com/help/$1')
1994 | } }|
1995 | Sort-Object -Property 'Title (KB)' -Unique |
1996 | Sort-Object -Property 'Installation time' -Descending
1997 | } catch {}
1998 |
1999 | if ($null -eq $updatelist) {
2000 | $htmlTable = "Windows Update history returned no results.
.NET Framework Updates may not have been installed to date.
"
2001 |
2002 | } else {
2003 | $htmlTable = $updatelist | convertto-html -Property 'Installation time','KB Number', 'Title (KB)', 'Support Url' -Fragment
2004 | }
2005 | return $htmlTable
2006 | }
2007 |
2008 | function Get-InstalledWindowsUpdates {
2009 | #we use get-hotfix to get the classic output of installed updates it will not return optional updates though
2010 | $htmlTable = get-hotfix |
2011 | Sort-Object -Descending InstalledOn |
2012 | select-object @{Name='Support Url'; Expression={$_.Caption}},
2013 | @{Name='Update type'; Expression={$_.Description}},
2014 | @{Name='KB Number'; Expression={$_.HotFixId}},
2015 | @{Name='Installation time'; Expression={$_.InstalledOn}},
2016 | InstalledBy |
2017 | convertto-html -Property 'Installation time','Update type','KB Number',InstalledBy,'Support Url' -Fragment
2018 |
2019 | return $htmlTable
2020 | }
2021 |
2022 | function Get-WindowsUpdateHTMLReport {
2023 | $htmlTemplate = @"
2024 |
2025 |
2026 |
2072 |
2073 |
2074 | Hotfix Information for: $env:COMPUTERNAME
2075 | $(Get-InstalledWindowsUpdates)
2076 | .Net Framework Cumulative Updates - History (last 5)
2077 | $(Get-NetframeworkInstalledUpdates)
2078 |
2079 |
2080 | "@
2081 |
2082 | return $htmlTemplate
2083 | }
2084 | #endregion
2085 | ##########################################################################
2086 | #region Execution
2087 |
2088 | if (IsAdminAccount){
2089 | Write-host "Script is executed as Administrator. Resuming execution" -ForegroundColor Green
2090 |
2091 | if ([string]::IsNullOrEmpty($Path)) {
2092 | $RunProp = RunDialog
2093 | $Path = $RunProp.Path.ToString()
2094 | $TraceEnabled = $RunProp.TraceEnabled
2095 | $NetTraceEnabled = $RunProp.NetTraceEnabled
2096 | $ConfigOnly = $RunProp.ConfigOnly
2097 | $PerfCounter = $RunProp.PerfCounter
2098 | $LdapTraceEnabled= $RunProp.LdapTraceEnabled
2099 | $WAPTraceEnabled= $RunProp.WAPTraceEnabled
2100 | }
2101 | elseif (![string]::IsNullOrEmpty($Path)) {
2102 | if($Tracing.IsPresent -eq $false){ $TraceEnabled=$false;$NetTraceEnabled=$false;$PerfCounter=$false;$LdapTraceEnabled=$false;$ConfigOnly=$true;$WAPTraceEnabled=$false }
2103 | else {
2104 | $TraceEnabled=$true;
2105 | $ConfigOnly=$false;
2106 | $LdapTraceEnabled=$false
2107 | $WAPTraceEnabled=$false
2108 | $PerfCounter=$false
2109 | if($NetworkTracing.IsPresent -eq $true){ $NetTraceEnabled=$true } else { $NetTraceEnabled=$false }
2110 | if($PerfTracing.IsPresent -eq $true) { $PerfCounter=$true }
2111 | if(($LDAPTracing.IsPresent -eq $true) -and (!$IsProxy)) { $LdapTraceEnabled=$true }
2112 | if(($WAPTracing.IsPresent -eq $true) -and ($IsProxy)) { $WAPTraceEnabled=$true }
2113 | }
2114 | }
2115 |
2116 | if(Test-Path -Path $Path) { Write-host "Your folder: $Path already exists. Starting Data Collection..." -ForegroundColor DarkCyan }
2117 | else {
2118 | Write-host "Your Logfolder: $Path does not exist. Creating Folder" -ForegroundColor DarkCyan
2119 | New-Item -ItemType directory -Path $Path -Force | Out-Null
2120 | }
2121 | $FEL=$Global:FormatEnumerationLimit ##secure current EnumLimit.Script should revert to this value at the end of execution
2122 | $Global:FormatEnumerationLimit=-1
2123 |
2124 | $TraceDir = $Path +"\temporary"
2125 | # Save execution output to file
2126 | Write-host "Creating Temporary Folder in $path" -ForegroundColor DarkCyan
2127 | New-Item -ItemType directory -Path $TraceDir -Force | Out-Null
2128 | if($PSVersionTable.PSVersion -le [Version]'4.0') { Start-Transcript -Path "$TraceDir\transscript_output.txt" -Append |out-null} else { Start-Transcript -Path "$TraceDir\transscript_output.txt" -Append -IncludeInvocationHeader |out-null}
2129 | Write-Host "Debug logs will be saved in: " $Path -ForegroundColor DarkCyan
2130 | Write-Host "Options selected: TracingEnabled:"$TraceEnabled "NetworkTrace:" $NetTraceEnabled " ConfigOnly:" $ConfigOnly " PerfCounter:" $PerfCounter " LDAPTrace:" $LdapTraceEnabled "WAPTrace:" $WAPTraceEnabled -ForegroundColor DarkCyan
2131 | Write-Progress -Activity "Preparation" -Status 'Setup Data Directory' -percentcomplete 5
2132 |
2133 | if ($TraceEnabled) {
2134 | $MessageTitle = "Initialization completed`n"
2135 | $MessageIse = "Data Collection is ready to start.`nPrepare other computers to start collecting data.`n`nWhen ready, Click OK to start the collection...`n"
2136 | $MessageC = "`nData Collection is ready to start.`nPrepare other computers to start collecting data.`n`nWhen ready, press CTRL+Y to start the collection...`n"
2137 | Pause $MessageIse $MessageTitle $MessageC
2138 | }
2139 |
2140 | Write-Host "Tracing is starting. Current UTC time: $([DateTime]::UtcNow)" -ForegroundColor Cyan
2141 | Write-Host "Timezone: $([System.TimeZoneInfo]::Local.StandardName)" -ForegroundColor DarkCyan
2142 |
2143 | Write-Progress -Activity "Gathering Configuration Data" -Status 'Getting ADFS Configuration' -percentcomplete 7
2144 | GetADFSConfig
2145 | GetDRSConfig
2146 | Clear-DnsClientCache
2147 |
2148 |
2149 | Write-Progress -Activity "Enable Logging" -Status 'Eventlogs' -percentcomplete 15
2150 | $starttime = (get-date)
2151 |
2152 | Write-host "Configuring Event Logging" -ForegroundColor DarkCyan
2153 | if ($IsProxy) { EnableDebugEvents $WAPDebugEvents }
2154 | else { EnableDebugEvents $ADFSDebugEvents }
2155 |
2156 | Write-Progress -Activity "Enable Logging" -Status 'Netlogon Debug Logging' -percentcomplete 30
2157 | EnableNetlogonDebug
2158 |
2159 | Write-Progress -Activity "Enable Logging" -Status 'Additional ETL Logging' -percentcomplete 40
2160 | LogManStart
2161 | EnableNetworkTrace
2162 | EnablePerfCounter
2163 | EnableLDAPTrace
2164 | EnableWAPTrace
2165 |
2166 | if($TraceEnabled) {
2167 | Write-Progress -Activity "Ready for Repro" -Status 'Waiting for Repro' -percentcomplete 50
2168 | $MessageTitle = "Data Collection Running"
2169 | $MessageIse = "Data Collection is currently running`nProceed reproducing the problem now or`n`nPress OK to stop the collection...`n"
2170 | $MessageC = "Data Collection is currently running`nProceed reproducing the problem now or `n`nPress CTRL+Y to stop the collection...`n"
2171 | Pause $MessageIse $MessageTitle $MessageC
2172 | }
2173 |
2174 | Write-Progress -Activity "Collecting" -Status 'Stop Event logging' -percentcomplete 55
2175 | if ($IsProxy) { DisableDebugEvents $WAPDebugEvents }
2176 | else { DisableDebugEvents $ADFSDebugEvents }
2177 |
2178 | Write-Progress -Activity "Collecting" -Status 'Stop additional logs' -percentcomplete 65
2179 | LogManStop
2180 | DisableNetworkTrace
2181 | DisablePerfCounter
2182 | DisableNetlogonDebug
2183 | DisableLDAPTrace
2184 | DisableWAPTrace
2185 |
2186 | Write-Progress -Activity "Collecting" -Status 'Getting otherlogs' -percentcomplete 70
2187 | GatherTheRest
2188 |
2189 | Write-Host "Tracing has completed. Current UTC time: $([DateTime]::UtcNow)" -ForegroundColor Cyan
2190 | Write-Progress -Activity "Collecting" -Status 'Exporting Eventlogs' -percentcomplete 85
2191 | [int]$endtimeinmsec= (New-TimeSpan -start $starttime -end (get-date).AddMinutes(5)).TotalMilliseconds
2192 |
2193 | if ($IsProxy) { ExportEventLogs $WAPExportEvents $endtimeinmsec }
2194 | else { ExportEventLogs $ADFSExportEvents $endtimeinmsec }
2195 | $Global:FormatEnumerationLimit=$FEL
2196 | Write-Progress -Activity "Saving" -Status 'Compressing Files - This may take some moments to complete' -percentcomplete 95
2197 | Write-host "Almost done. We are compressing all Files. Please wait" -ForegroundColor Green
2198 | EndOfCollection
2199 |
2200 | }
2201 | else {
2202 | Write-Host "You do not have Administrator rights!`nPlease re-run this script as an Administrator!" -ForegroundColor Red
2203 | Break
2204 | }
2205 | #endregion
2206 |
--------------------------------------------------------------------------------