diff --git a/Config/Settings.json b/Config/Settings.json index 4c75000..ea44f1c 100644 --- a/Config/Settings.json +++ b/Config/Settings.json @@ -16,6 +16,7 @@ } ], "UseADModule": false, + "UseLDAPS": false, "DemoMode": true, "LogPath": "Logs/error_log.txt", "LoggingEnabled": true, diff --git a/Config/Settings.ps1 b/Config/Settings.ps1 index 31fa6f8..bf0ff7a 100644 --- a/Config/Settings.ps1 +++ b/Config/Settings.ps1 @@ -4,6 +4,7 @@ $script:AppSettings = $null $settingsTemplate = @{ DemoMode = $false UseADModule = $true + UseLDAPS = $false DefaultDomain = $env:USERDNSDOMAIN AutoReplyTemplate = "I am currently unavailable..." LoggingEnabled = $true diff --git a/Functions/Core/Dependencies/DotNetVersionCheck.ps1 b/Functions/Core/Dependencies/DotNetVersionCheck.ps1 index 26d481c..2fa8d03 100644 --- a/Functions/Core/Dependencies/DotNetVersionCheck.ps1 +++ b/Functions/Core/Dependencies/DotNetVersionCheck.ps1 @@ -45,3 +45,46 @@ function Load-LdapLibrary { throw "Unsupported environment: $Environment" } } + +function Initialize-LDAPAssemblies { + try { + Write-Host "Checking LDAP assemblies..." + + # Check .NET Framework version + $dotNetVersion = [System.Environment]::Version + Write-Host "Detected .NET version: $dotNetVersion" + + if ($dotNetVersion.Major -lt 4) { + throw ".NET Framework 4.0 or higher is required for LDAP operations" + } + + # Try to load the assembly if not already loaded + $ldapAssembly = [System.AppDomain]::CurrentDomain.GetAssemblies() | + Where-Object { $_.FullName -like "System.DirectoryServices.Protocols*" } + + if (-not $ldapAssembly) { + Write-Host "Loading System.DirectoryServices.Protocols assembly..." + try { + Add-Type -AssemblyName System.DirectoryServices.Protocols + Write-Host "Successfully loaded LDAP assemblies" + } + catch { + throw "Failed to load System.DirectoryServices.Protocols assembly: $($_.Exception.Message)" + } + } + else { + Write-Host "LDAP assemblies already loaded" + } + + # Verify we can access the types we need + $null = [System.DirectoryServices.Protocols.LdapConnection] + $null = [System.DirectoryServices.Protocols.SearchRequest] + + return $true + } + catch { + Write-Host "Failed to initialize LDAP assemblies: $($_.Exception.Message)" + Write-Host "This might affect LDAPS functionality" + return $false + } +} diff --git a/Functions/Core/Environment.ps1 b/Functions/Core/Environment.ps1 index 9de78c0..eb42830 100644 --- a/Functions/Core/Environment.ps1 +++ b/Functions/Core/Environment.ps1 @@ -24,6 +24,6 @@ } } - Write-Host "Using LDAP for directory access" + Write-Host "Using LDAP(S) for directory access" return $true } \ No newline at end of file diff --git a/Functions/Data/LDAP/LDAPConnection.ps1 b/Functions/Data/LDAP/LDAPConnection.ps1 index d35726f..126a69b 100644 --- a/Functions/Data/LDAP/LDAPConnection.ps1 +++ b/Functions/Data/LDAP/LDAPConnection.ps1 @@ -1,76 +1,148 @@ -function Get-LDAPConnection { - param ( - [Parameter(Mandatory=$true)] +function Test-LDAPSConnection { + param( [string]$DomainController, - [Parameter(Mandatory=$true)] - [System.Management.Automation.PSCredential]$Credential + [int]$Port = 636 ) try { - Write-Host "Connecting with DC: $($DomainController)" - $networkCred = $Credential.GetNetworkCredential() + Write-Host "Testing LDAPS prerequisites..." - # Try LDAPS first (SSL) - $ldapPath = "LDAPS://$DomainController" - $authType = [System.DirectoryServices.AuthenticationTypes]::SecureSocketsLayer - - # Write-Host "Attempting secure LDAPS connection..." - # try { - # $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry( - # $ldapPath, - # $Credential.UserName, - # $networkCred.Password, - # $authType - # ) - - # # Test connection - # $null = $directoryEntry.NativeObject - # Write-Host "LDAPS connection successful" + # Test if port 636 is open + $tcpClient = New-Object System.Net.Sockets.TcpClient + $connectionResult = $tcpClient.BeginConnect($DomainController, $Port, $null, $null) + $waited = $connectionResult.AsyncWaitHandle.WaitOne(1000, $false) + + if (-not $waited) { + Write-Host "Port $Port is not accessible on $DomainController" + return $false + } + + Write-Host "Port $Port is open on $DomainController" + + # Test SSL certificate + $cert = $null + try { + $tcpClient = New-Object System.Net.Sockets.TcpClient($DomainController, $Port) + $sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream()) + $sslStream.AuthenticateAsClient($DomainController) + $cert = $sslStream.RemoteCertificate - # return $directoryEntry - # } - # catch { - # Write-Host "LDAPS connection failed, trying standard LDAP..." - Write-Host "LDAPS connection failed, currently unsupported..." - # If LDAPS fails, try regular LDAP - $ldapPath = "LDAP://$DomainController" - $authType = [System.DirectoryServices.AuthenticationTypes]::Secure -bor - [System.DirectoryServices.AuthenticationTypes]::Sealing -bor - [System.DirectoryServices.AuthenticationTypes]::Signing - - $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry( - $ldapPath, - "$($networkCred.Domain)\$($networkCred.Username)", - $networkCred.Password, - $authType - ) - # Test connection using NativeObject - $null = $directoryEntry.NativeObject - $script:searchBase = Set-RootSearchBase -DomainController $DomainController - Write-Host "LDAP connection successful" - return $directoryEntry - # } + Write-Host "SSL Certificate Details:" + Write-Host "Subject: $($cert.Subject)" + Write-Host "Issuer: $($cert.Issuer)" + Write-Host "Valid From: $($cert.NotBefore)" + Write-Host "Valid To: $($cert.NotAfter)" + Write-Host "Thumbprint: $($cert.Thumbprint)" + } + catch { + Write-Host "SSL Certificate error: $($_.Exception.Message)" + return $false + } + finally { + if ($sslStream) { $sslStream.Dispose() } + if ($tcpClient) { $tcpClient.Dispose() } + } + + return $true } catch { - Write-Host "LDAP connection error: $($_.Exception.Message)" - throw + Write-Host "LDAPS test error: $($_.Exception.Message)" + return $false } } -function Set-RootSearchBase { +function Get-LDAPConnection { param ( [Parameter(Mandatory=$true)] - [string]$DomainController + [string]$DomainController, + [Parameter(Mandatory=$true)] + [System.Management.Automation.PSCredential]$Credential, + [Parameter(Mandatory=$false)] + [bool]$UseLDAPS = $false ) + try { - # Retrieve the default search base - $rootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/RootDSE") - Write-Host "Default search base: $searchBase" - return $rootDSE.Properties["defaultNamingContext"][0] + Write-Host "Connecting with DC: $($DomainController)" + $networkCred = $Credential.GetNetworkCredential() + + # Convert domain to DC format + $dcPath = "DC=" + ($networkCred.Domain.Split('.') -join ',DC=') + + # If LDAPS is requested, try it first + if ($UseLDAPS) { + try { + Write-Host "LDAPS connection requested" + + # Test LDAPS prerequisites + if (-not (Test-LDAPSConnection -DomainController $DomainController)) { + Write-Host "LDAPS prerequisites not met, falling back to LDAP" + throw "LDAPS prerequisites not met" + } + + $ldapPath = "LDAPS://$DomainController/$dcPath" + Write-Host "Attempting LDAPS connection to: $ldapPath" + + $authType = [System.DirectoryServices.AuthenticationTypes]::Secure -bor + [System.DirectoryServices.AuthenticationTypes]::Sealing -bor + [System.DirectoryServices.AuthenticationTypes]::Signing -bor + [System.DirectoryServices.AuthenticationTypes]::SecureSocketsLayer + + # Create callback to ignore certificate validation + $callback = [System.Net.Security.RemoteCertificateValidationCallback]{ + param($sender, $certificate, $chain, $sslPolicyErrors) + return $true + } + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $callback + + $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry( + $ldapPath, + "$($networkCred.Domain)\$($networkCred.Username)", + $Credential.GetNetworkCredential().Password, + $authType + ) + + # Test the connection + $null = $directoryEntry.NativeObject + Write-Host "LDAPS connection successful" + + return @{ + Connection = $directoryEntry + IsLDAPS = $true + Credentials = $Credential + } + } + catch { + Write-Host "LDAPS connection failed: $($_.Exception.Message)" + Write-Host "Falling back to standard LDAP..." + } + } + + # Standard LDAP connection (fallback or primary if LDAPS not requested) + Write-Host "Attempting standard LDAP connection..." + $ldapPath = "LDAP://$DomainController" + $authType = [System.DirectoryServices.AuthenticationTypes]::Secure -bor + [System.DirectoryServices.AuthenticationTypes]::Sealing -bor + [System.DirectoryServices.AuthenticationTypes]::Signing + + $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry( + $ldapPath, + "$($networkCred.Domain)\$($networkCred.Username)", + $Credential.GetNetworkCredential().Password, + $authType + ) + + # Test the connection + $null = $directoryEntry.NativeObject + Write-Host "LDAP connection successful" + + return @{ + Connection = $directoryEntry + IsLDAPS = $false + Credentials = $Credential + } } catch { - Write-Host "Retrieving seachbase failed: $($_.Exception.Message)" + Write-Host "All connection attempts failed: $($_.Exception.Message)" throw } - } \ No newline at end of file diff --git a/Functions/Data/LDAP/LDAPUsers.ps1 b/Functions/Data/LDAP/LDAPUsers.ps1 index 00eac69..55c22b3 100644 --- a/Functions/Data/LDAP/LDAPUsers.ps1 +++ b/Functions/Data/LDAP/LDAPUsers.ps1 @@ -1,16 +1,76 @@ function Get-LDAPUsers { param ( - [System.DirectoryServices.DirectoryEntry]$Directory, + [Parameter(Mandatory=$true)] + $Directory, [string]$SearchFilter = "(objectClass=user)" ) - $searcher = New-Object System.DirectoryServices.DirectorySearcher($Directory) - $searcher.Filter = $SearchFilter - $searcher.PropertiesToLoad.AddRange(@( - "userPrincipalName", "displayName", "mail", "department", - "title", "manager", "telephoneNumber", "mobile", - "whenCreated", "whenChanged", "memberOf", "userAccountControl" - )) - - return $searcher.FindAll() + try { + if ($Directory.IsLDAPS) { + Write-Host "Using System.DirectoryServices.Protocols for LDAPS..." + + # Extract server name from path + $serverName = $Directory.Connection.Path -replace 'LDAPS://', '' + Write-Host "Server name: $serverName" + + # Get credentials from directory entry + $username = $Directory.Connection.Username + $password = $Directory.Connection.Password + Write-Host "Using credentials for: $username" + + # Create LDAP identifier + $identifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($serverName, 636) + + # Create network credential + $networkCred = New-Object System.Net.NetworkCredential($username, $password) + + # Create and configure LDAP connection + $ldapConnection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier) + $ldapConnection.Credential = $networkCred + $ldapConnection.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate + $ldapConnection.SessionOptions.ProtocolVersion = 3 + $ldapConnection.SessionOptions.SecureSocketLayer = $true + + Write-Host "Created LDAPS connection, attempting search..." + + # Create the search request + $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest + $searchRequest.DistinguishedName = "" # Root + $searchRequest.Filter = $SearchFilter + $searchRequest.Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree + + # Add attributes to retrieve + @( + "userPrincipalName", "displayName", "mail", "department", + "title", "manager", "telephoneNumber", "mobile", + "whenCreated", "whenChanged", "memberOf", "userAccountControl" + ) | ForEach-Object { + $searchRequest.Attributes.Add($_) | Out-Null + } + + Write-Host "Executing LDAPS search..." + $timeSpan = New-Object System.TimeSpan(0, 0, 30) # 30 seconds timeout + $response = $ldapConnection.SendRequest($searchRequest, $timeSpan) + Write-Host "Search completed successfully" + + return $response.Entries + } + else { + Write-Host "Using DirectorySearcher for standard LDAP..." + $searcher = New-Object System.DirectoryServices.DirectorySearcher($Directory.Connection) + $searcher.Filter = $SearchFilter + $searcher.PropertiesToLoad.AddRange(@( + "userPrincipalName", "displayName", "mail", "department", + "title", "manager", "telephoneNumber", "mobile", + "whenCreated", "whenChanged", "memberOf", "userAccountControl" + )) + + return $searcher.FindAll() + } + } + catch { + Write-Host "Error in Get-LDAPUsers: $($_.Exception.Message)" + Write-Host "Stack trace: $($_.ScriptStackTrace)" + throw + } } \ No newline at end of file diff --git a/Functions/UI/O365/Initialize-O365Tab.ps1 b/Functions/UI/O365/Initialize-O365Tab.ps1 index fb24576..7640cae 100644 --- a/Functions/UI/O365/Initialize-O365Tab.ps1 +++ b/Functions/UI/O365/Initialize-O365Tab.ps1 @@ -1,3 +1,13 @@ +function Update-O365Dropdowns { + Write-Host "Refreshing O365 dropdowns..." + # Add AD users to forwarding dropdown + Update-ForwardingUserList + # Add AD users to teamsowner dropdown + Update-TeamsOwnerList + # Initialize license target combobox + Update-LicenseTargetList +} + function Initialize-O365Tab { param ( [System.Windows.Window]$Window, @@ -83,12 +93,7 @@ function Initialize-O365Tab { $script:lstProducts.Items.Clear() $script:lstProducts.Items.Add("Please connect to O365 first...") } - # Add AD users to forwarding dropdown - Update-ForwardingUserList - # Add AD users to teamsowner dropdown - Update-TeamsOwnerList - # Initialize license target combobox - Update-LicenseTargetList + Update-O365Dropdowns # Add click handler for connect button $script:btnConnectO365.Add_Click({ diff --git a/Functions/UI/Windows/Login/LoginDialog.ps1 b/Functions/UI/Windows/Login/LoginDialog.ps1 index 9c992d1..3d3862e 100644 --- a/Functions/UI/Windows/Login/LoginDialog.ps1 +++ b/Functions/UI/Windows/Login/LoginDialog.ps1 @@ -106,7 +106,8 @@ } else { try { # Use Get-LDAPConnection for authentication - $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential + $useLDAPS = Get-AppSetting -SettingName "UseLDAPS" + $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential -UseLDAPS $useLDAPS Update-LoadingMessage -LoadingWindow $loadingWindow -Message "Connection successful..." Start-Sleep -Milliseconds 500 diff --git a/Functions/UI/Windows/Main/Show-UserDetails.ps1 b/Functions/UI/Windows/Main/Show-UserDetails.ps1 index 02f16d6..2b84b91 100644 --- a/Functions/UI/Windows/Main/Show-UserDetails.ps1 +++ b/Functions/UI/Windows/Main/Show-UserDetails.ps1 @@ -62,7 +62,8 @@ $($User.MemberOf | ForEach-Object { "- $_" } | Out-String) "@ } else { - $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential + $useLDAPS = Get-AppSetting -SettingName "UseLDAPS" + $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential -UseLDAPS $useLDAPS $filter = "(&(objectClass=user)(userPrincipalName=$UserPrincipalName))" $User = Get-LDAPUsers -Directory $directory -SearchFilter $filter | Select-Object -First 1 diff --git a/Functions/UI/Windows/Main/Update-SelectedUser.ps1 b/Functions/UI/Windows/Main/Update-SelectedUser.ps1 index dbaa8f1..dd82931 100644 --- a/Functions/UI/Windows/Main/Update-SelectedUser.ps1 +++ b/Functions/UI/Windows/Main/Update-SelectedUser.ps1 @@ -16,7 +16,8 @@ function Update-SelectedUser { $script:SelectedUser = Get-ADUser -Credential $Credential -Filter "UserPrincipalName -eq '$UserPrincipalName'" -Properties * } else { - $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential + $useLDAPS = Get-AppSetting -SettingName "UseLDAPS" + $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential -UseLDAPS $useLDAPS $filter = "(&(objectClass=user)(userPrincipalName=$UserPrincipalName))" $script:SelectedUser = Get-LDAPUsers -Directory $directory -SearchFilter $filter | Select-Object -First 1 } diff --git a/Functions/UI/Windows/Main/Update-UserList.ps1 b/Functions/UI/Windows/Main/Update-UserList.ps1 index 10c6d9e..205be97 100644 --- a/Functions/UI/Windows/Main/Update-UserList.ps1 +++ b/Functions/UI/Windows/Main/Update-UserList.ps1 @@ -55,26 +55,58 @@ function Update-UserList { } } else { - $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential - $filter = "(&(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!(userAccountControl:1.2.840.113556.1.4.803:=16))(mail=*))" - - if ($SearchText) { - $filter = "(&(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!(userAccountControl:1.2.840.113556.1.4.803:=16))(mail=*)(userPrincipalName=*$SearchText*))" - } - - Write-Host "Using LDAP filter: $filter" - - $script:Users = Get-LDAPUsers -Directory $directory -SearchFilter $filter + try { + $useLDAPS = Get-AppSetting -SettingName "UseLDAPS" + $directory = Get-LDAPConnection -DomainController $script:DomainController -Credential $Credential -UseLDAPS $useLDAPS + + $filter = "(&(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!(userAccountControl:1.2.840.113556.1.4.803:=16))(mail=*))" + + if ($SearchText) { + $filter = "(&(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!(userAccountControl:1.2.840.113556.1.4.803:=16))(mail=*)(userPrincipalName=*$SearchText*))" + } - $ListBox.Items.Clear() - foreach ($User in $script:Users) { - if ($User.Properties["userPrincipalName"]) { - $ListBox.Items.Add($User.Properties["userPrincipalName"][0]) + Write-Host "Using LDAPS filter: $filter" + + $script:Users = Get-LDAPUsers -Directory $directory -SearchFilter $filter + + $ListBox.Items.Clear() + foreach ($User in $script:Users) { + if ($directory.IsLDAPS) { + # Handle System.DirectoryServices.Protocols response + $attributes = $User.Attributes + if ($attributes["userPrincipalName"]) { + $ListBox.Items.Add($attributes["userPrincipalName"][0]) + } + } + else { + # Handle standard DirectorySearcher response + if ($User.Properties["userPrincipalName"]) { + $ListBox.Items.Add($User.Properties["userPrincipalName"][0]) + } + } + } + } + catch { + Write-Host "Failed to update user list: $($_.Exception.Message)" + throw + } + finally { + if ($directory -and $directory.Connection) { + try { + [System.Runtime.InteropServices.Marshal]::ReleaseComObject($directory.Connection) + } + catch { + Write-Host "Warning: Connection cleanup error: $($_.Exception.Message)" + } } } } } + if ($script:txtO365Results) { # Check if O365 tab is initialized + Write-Host "Updating O365 dropdowns after user list update..." + Update-O365Dropdowns + } } catch { Write-Error "Failed to update user list: $_" diff --git a/Functions/UI/Windows/Settings/SettingsHandler.ps1 b/Functions/UI/Windows/Settings/SettingsHandler.ps1 index 6b2d4af..114bf9f 100644 --- a/Functions/UI/Windows/Settings/SettingsHandler.ps1 +++ b/Functions/UI/Windows/Settings/SettingsHandler.ps1 @@ -50,6 +50,7 @@ function Save-Settings { $txtDefaultDomain = $Window.FindName("txtDefaultDomain") $txtAutoReplyTemplate = $Window.FindName("txtAutoReplyTemplate") $txtSettingsStatus = $Window.FindName("txtSettingsStatus") + $chkUseLDAPS = $Window.FindName("chkUseLDAPS") # Get current settings $currentSettings = Get-AppSetting @@ -58,6 +59,7 @@ function Save-Settings { $settings = @{ DemoMode = $chkDemoMode.IsChecked UseADModule = $chkUseADModule.IsChecked + UseLDAPS = $chkUseLDAPS.IsChecked DefaultDomain = if (![string]::IsNullOrWhiteSpace($txtDefaultDomain.Text)) { $txtDefaultDomain.Text } else { diff --git a/Functions/UI/Windows/Settings/Show-SettingsWindow.ps1 b/Functions/UI/Windows/Settings/Show-SettingsWindow.ps1 index 8487274..e504ed5 100644 --- a/Functions/UI/Windows/Settings/Show-SettingsWindow.ps1 +++ b/Functions/UI/Windows/Settings/Show-SettingsWindow.ps1 @@ -21,6 +21,7 @@ function Show-SettingsWindow { $txtDefaultDomain = $settingsWindow.FindName("txtDefaultDomain") $txtAutoReplyTemplate = $settingsWindow.FindName("txtAutoReplyTemplate") $btnSaveSettings = $settingsWindow.FindName("btnSaveSettings") + $chkUseLDAPS = $SettingsWindow.FindName("chkUseLDAPS") $btnClose = $settingsWindow.FindName("btnClose") $txtSettingsStatus = $settingsWindow.FindName("txtSettingsStatus") @@ -31,6 +32,7 @@ function Show-SettingsWindow { # Apply settings to UI $chkDemoMode.IsChecked = $settings.DemoMode $chkUseADModule.IsChecked = $settings.UseADModule + $chkUseLDAPS.IsChecked = $settings.UseLDAPS # Only populate text fields if they have values in settings if (![string]::IsNullOrWhiteSpace($settings.DefaultDomain)) { diff --git a/Logs/error_log.txt b/Logs/error_log.txt index e08b0dd..d3f5a12 100644 Binary files a/Logs/error_log.txt and b/Logs/error_log.txt differ diff --git a/README.md b/README.md index c3d0f77..2de655c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ The tool automatically adapts to your system architecture: - ✅ **x86 (32-bit)**: Full AD PowerShell module support - ✅ **ARM64**: LDAP-based access (Windows 11 ARM) +Use of LDAPS can be set in settings + ## Features - 🖥️ Modern WPF interface with sleek styling - 🔒 Secure authentication for both AD and O365 diff --git a/Start-Offboarding.ps1 b/Start-Offboarding.ps1 index 833cf19..c80830b 100644 --- a/Start-Offboarding.ps1 +++ b/Start-Offboarding.ps1 @@ -124,8 +124,7 @@ if ($currentVersion -lt $minVersion) { try { $psEnvironment = Check-PowerShellVersion Write-Host "Running on PowerShell $psEnvironment" - # Load-LdapLibrary -Environment $psEnvironment - # Proceed with your script logic here + $script:UseProtocolsForLDAPS = Initialize-LDAPAssemblies Write-Host "Environment validated. Proceeding with script execution..." -ForegroundColor Green } catch { Write-Error $_.Exception.Message diff --git a/XAML/Windows/SettingsWindow.xaml b/XAML/Windows/SettingsWindow.xaml index fc65226..160b2e9 100644 --- a/XAML/Windows/SettingsWindow.xaml +++ b/XAML/Windows/SettingsWindow.xaml @@ -137,6 +137,9 @@ Content="Use AD Module" Foreground="$($colors.Text)" Margin="0,0,0,10"/> +