diff --git a/Extensions/Add-XADGroupMember.ps1 b/Extensions/Add-XADGroupMember.ps1 index e2610dc..21ea0af 100644 --- a/Extensions/Add-XADGroupMember.ps1 +++ b/Extensions/Add-XADGroupMember.ps1 @@ -1,8 +1,8 @@ function Add-XADGroupMember { param ([Parameter(Mandatory = $true)][string]$Domain) - $Username = read-host -Prompt "Username" - $Group = read-host -Prompt "Group" + $Username = Read-Host -Prompt "Username" + $Group = Read-Host -Prompt "Group" Write-Host "`nAdding '$Username' to $Group in $Domain..............`n" -ForegroundColor Yellow try { Add-ADGroupMember $Group -Members $Username -ErrorAction Stop @@ -13,3 +13,9 @@ function Add-XADGroupMember { Write-Host "Adding '$Username' to $Group in $Domain domain failed. ErrorDetails: $ErrorDetails" -ForegroundColor Red } } + + + + + + diff --git a/Extensions/New-XADAdmin.ps1 b/Extensions/New-XADAdmin.ps1 index 4cde170..75606a9 100644 --- a/Extensions/New-XADAdmin.ps1 +++ b/Extensions/New-XADAdmin.ps1 @@ -1,42 +1,54 @@ function New-XADAdmin { + [CmdletBinding(SupportsShouldProcess = $true)] param ([Parameter(Mandatory = $true)][string]$Domain) + begin {} + process { + if ($pscmdlet.ShouldProcess("domain - $Domain")) { + $DomainDNRoot = (Get-ADDomain).DistinguishedName + $DomainDNSSuffix = (Get-ADDomain).DNSRoot - $DomainDNRoot = (Get-ADDomain).DistinguishedName - $DomainDNSSuffix = (Get-ADDomain).DNSRoot + $Username = Read-Host -Prompt "Username" + $Password = Read-Host -Prompt "Password" -AsSecureString + $Description = Read-Host -Prompt "Description" + $Mobile = Read-Host -Prompt "Mobile" - $Username = read-host -Prompt "Username" - $Password = read-host -Prompt "Password" -AsSecureString - $Description = read-host -Prompt "Description" - $Mobile = read-host -Prompt "Mobile" + $Path = Read-Host -Prompt "Enter the OU name for admin users (default: Admin Accounts)" + if ([string]::IsNullOrWhiteSpace($Path)) { + $Path = "Admin Accounts" + } + $Path = $Path.Trim() + $Path = "OU=" + $Path + "," + $DomainDNRoot - $Path = Read-Host -Prompt "Enter the OU name for admin users (default: Admin Accounts)" - if ([string]::IsNullOrWhiteSpace($Path)) { - $Path = "Admin Accounts" - } - $Path = $Path.Trim() - $Path = "OU=" + $Path + "," + $DomainDNRoot - - Write-Host "`nCreating new admin user '$Username' in $Domain under $Path..............`n" -ForegroundColor Yellow - $UserParams = @{ - Name = $UserName - GivenName = $UserName - Surname = "" - UserPrincipalName = "$UserName@$DomainDNSSuffix" - SamAccountName = $UserName - Description = $Description - DisplayName = $UserName - Path = $Path - AccountPassword = $Password - Enabled = $true - Mobile = $Mobile - } - try { - New-ADUser @UserParams -ErrorAction Stop - Write-Host "Account creation succeeded for '$Username' in $Domain domain." -ForegroundColor Green - } - catch { - $ErrorDetails = $_.Exception.Message - Write-Host "Account creation failed for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + Write-Host "`nCreating new admin user '$Username' in $Domain under $Path..............`n" -ForegroundColor Yellow + $UserParams = @{ + Name = $UserName + GivenName = $UserName + Surname = "" + UserPrincipalName = "$UserName@$DomainDNSSuffix" + SamAccountName = $UserName + Description = $Description + DisplayName = $UserName + Path = $Path + AccountPassword = $Password + Enabled = $true + Mobile = $Mobile + } + try { + New-ADUser @UserParams -ErrorAction Stop + Write-Host "Account creation succeeded for '$Username' in $Domain domain." -ForegroundColor Green + } + catch { + $ErrorDetails = $_.Exception.Message + Write-Host "Account creation failed for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + } + } } + end {} } + + + + + + diff --git a/Extensions/New-XADServiceAccount.ps1 b/Extensions/New-XADServiceAccount.ps1 index ec97f65..f4b32ad 100644 --- a/Extensions/New-XADServiceAccount.ps1 +++ b/Extensions/New-XADServiceAccount.ps1 @@ -1,55 +1,67 @@ function New-XADServiceAccount { + [CmdletBinding(SupportsShouldProcess = $true)] param ([Parameter(Mandatory = $true)][string]$Domain) + begin {} + process { + if ($pscmdlet.ShouldProcess("domain - $Domain")) { + $DomainDNRoot = (Get-ADDomain).DistinguishedName + $DomainDNSSuffix = (Get-ADDomain).DNSRoot - $DomainDNRoot = (Get-ADDomain).DistinguishedName - $DomainDNSSuffix = (Get-ADDomain).DNSRoot - - $OUPath = Read-Host -Prompt "Enter the name of the existing main OU (default: Service Accounts)" - if ([string]::IsNullOrWhiteSpace($OUPath)) { - $OUPath = "Service Accounts" - } - $OUPath = $OUPath.Trim() - $OUPath = "OU=" + $OUPath + "," + $DomainDNRoot - do { - $OUName = Read-Host -Prompt "Enter the name of the new sub OU" - } while ([string]::IsNullOrWhiteSpace($OUName)) - $OUName = $OUName.Trim() - Write-Host "`nCreating $OUName OU under $OUPath in $Domain domain..............`n" -ForegroundColor Yellow - try { - New-ADOrganizationalUnit -Name $OUName -Path $OUPath -ErrorAction Stop - Write-Host "OU creation succeeded for OU $OUName in $Domain domain." -ForegroundColor Green - } - catch { - $ErrorDetails = $_.Exception.Message - Write-Host "OU creation failed for $OUName in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red - } - do { - $Username = read-host -Prompt "Service Account Username" - } while ([string]::IsNullOrWhiteSpace($Username)) + $OUPath = Read-Host -Prompt "Enter the name of the existing main OU (default: Service Accounts)" + if ([string]::IsNullOrWhiteSpace($OUPath)) { + $OUPath = "Service Accounts" + } + $OUPath = $OUPath.Trim() + $OUPath = "OU=" + $OUPath + "," + $DomainDNRoot + do { + $OUName = Read-Host -Prompt "Enter the name of the new sub OU" + } while ([string]::IsNullOrWhiteSpace($OUName)) + $OUName = $OUName.Trim() + Write-Host "`nCreating $OUName OU under $OUPath in $Domain domain..............`n" -ForegroundColor Yellow + try { + New-ADOrganizationalUnit -Name $OUName -Path $OUPath -ErrorAction Stop + Write-Host "OU creation succeeded for OU $OUName in $Domain domain." -ForegroundColor Green + } + catch { + $ErrorDetails = $_.Exception.Message + Write-Host "OU creation failed for $OUName in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + } + do { + $Username = Read-Host -Prompt "Service Account Username" + } while ([string]::IsNullOrWhiteSpace($Username)) - $Password = read-host -Prompt "Service Account Password" -AsSecureString - $Description = read-host -Prompt "Service Account Description" - $Path = "OU=" + $OUName + "," + $OUPath; - $NewUserParams = @{ - Name = $Username - GivenName = $Username - Surname = "" - UserPrincipalName = "$Username@$DomainDNSSuffix" - SamAccountName = $Username - Description = $Description - DisplayName = $Username - Path = $Path - AccountPassword = $Password - Enabled = $true - PasswordNeverExpires = $true - } - Write-Host "`nCreating service account '$Username' in $Domain domain under $Path..............`n" -ForegroundColor Yellow - try { - New-ADUser @NewUserParams -ErrorAction Stop - Write-Host "Account creation succeeded for '$Username' in $Domain domain." -ForegroundColor Green - } - catch { - $ErrorDetails = $_.Exception.Message - Write-Host "Service Account creation failed for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + $Password = Read-Host -Prompt "Service Account Password" -AsSecureString + $Description = Read-Host -Prompt "Service Account Description" + $Path = "OU=" + $OUName + "," + $OUPath; + $NewUserParams = @{ + Name = $Username + GivenName = $Username + Surname = "" + UserPrincipalName = "$Username@$DomainDNSSuffix" + SamAccountName = $Username + Description = $Description + DisplayName = $Username + Path = $Path + AccountPassword = $Password + Enabled = $true + PasswordNeverExpires = $true + } + Write-Host "`nCreating service account '$Username' in $Domain domain under $Path..............`n" -ForegroundColor Yellow + try { + New-ADUser @NewUserParams -ErrorAction Stop + Write-Host "Account creation succeeded for '$Username' in $Domain domain." -ForegroundColor Green + } + catch { + $ErrorDetails = $_.Exception.Message + Write-Host "Service Account creation failed for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + } + } } + end {} } + + + + + + diff --git a/Extensions/Reset-XADUserPassword.ps1 b/Extensions/Reset-XADUserPassword.ps1 index 0bcbbbd..14e0d3e 100644 --- a/Extensions/Reset-XADUserPassword.ps1 +++ b/Extensions/Reset-XADUserPassword.ps1 @@ -1,32 +1,44 @@ function Reset-XADUserPassword { + [CmdletBinding(SupportsShouldProcess = $true)] param ([Parameter(Mandatory = $true)][string]$Domain) + begin {} + process { + if ($pscmdlet.ShouldProcess("domain - $Domain")) { + $Username = Read-Host -Prompt "Username" + Write-Host "`nFetching account details for '$Username' in $Domain domain..............`n" -ForegroundColor Yellow - $Username = read-host -Prompt "Username" - Write-Host "`nFetching account details for '$Username' in $Domain domain..............`n" -ForegroundColor Yellow - - try { - $Useraccount = Get-ADUser $Username -Properties mobile, PasswordLastSet, PasswordNeverExpires, PasswordExpired, msDS-UserPasswordExpiryTimeComputed -ErrorAction Stop - Write-Host "Account details for $Username from $Domain domain:" -ForegroundColor Green - $Useraccount | Select-Object mobile, PasswordLastSet, PasswordNeverExpires, PasswordExpired, @{Name = "PasswordExpiryDate"; Expression = { [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") } } - } - catch { - $ErrorDetails = $_.Exception.Message - Write-Host "Failed to fetch details for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red - return - } - $Password = read-host -Prompt "Password" -AsSecureString - $Confirm = Read-Host -Prompt "Are you sure you want to reset the password for $Username in $Domain domain?`n Type 'y' or 'Y' to continue" - if ($Confirm -notin 'y', 'Y') { - return + try { + $Useraccount = Get-ADUser $Username -Properties mobile, PasswordLastSet, PasswordNeverExpires, PasswordExpired, msDS-UserPasswordExpiryTimeComputed -ErrorAction Stop + Write-Host "Account details for $Username from $Domain domain:" -ForegroundColor Green + $Useraccount | Select-Object mobile, PasswordLastSet, PasswordNeverExpires, PasswordExpired, @{Name = "PasswordExpiryDate"; Expression = { [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") } } + } + catch { + $ErrorDetails = $_.Exception.Message + Write-Host "Failed to fetch details for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + return + } + $Password = Read-Host -Prompt "Password" -AsSecureString + $Confirm = Read-Host -Prompt "Are you sure you want to reset the password for $Username in $Domain domain?`n Type 'y' or 'Y' to continue" + if ($Confirm -notin 'y', 'Y') { + return + } + Write-Host "`nSetting password for '$Username' in $Domain domain..............`n" -ForegroundColor Yellow + try { + $Useraccount | Set-ADAccountPassword -NewPassword $Password -Reset -ErrorAction Stop + Write-Host "Password reset succeeded for '$Username' in $Domain domain." -ForegroundColor Green + } + catch { + $ErrorDetails = $_.Exception.Message + Write-Host "Password reset failed for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red + } + } } - Write-Host "`nSetting password for '$Username' in $Domain domain..............`n" -ForegroundColor Yellow - try { - $Useraccount | Set-ADAccountPassword -NewPassword $Password -Reset -ErrorAction Stop - Write-Host "Password reset succeeded for '$Username' in $Domain domain." -ForegroundColor Green - } - catch { - $ErrorDetails = $_.Exception.Message - Write-Host "Password reset failed for '$Username' in $Domain domain. ErrorDetails: $ErrorDetails" -ForegroundColor Red - } -} \ No newline at end of file + end {} +} + + + + + + diff --git a/Functions/Get-XADConfig.ps1 b/Functions/Get-XADConfig.ps1 index c9808ac..a2e5155 100644 --- a/Functions/Get-XADConfig.ps1 +++ b/Functions/Get-XADConfig.ps1 @@ -1,3 +1,8 @@ function Get-XADConfig { Import-Csv $script:DCIPsCSVPath -} \ No newline at end of file +} + + + + + diff --git a/Functions/New-XADDrive.ps1 b/Functions/New-XADDrive.ps1 index 9108474..2b77b4e 100644 --- a/Functions/New-XADDrive.ps1 +++ b/Functions/New-XADDrive.ps1 @@ -44,7 +44,7 @@ function New-XADDrive { )] [switch]$NoConnectionTest = $false ) - PROCESS { + process { foreach ($DomainController in $DomainControllers) { if ($NoConnectionTest) { $Server = $DomainController @@ -56,7 +56,7 @@ function New-XADDrive { break } } - if (-Not $Server) { + if (-not $Server) { $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Management.Automation.DriveNotFoundException]::new("Unable to create an Active Directory drive because none of the supplied domain controllers was reachable."), 'ADDriveCreationFailed', @@ -79,3 +79,9 @@ function New-XADDrive { $ADDrive } } + + + + + + diff --git a/Functions/Start-XADCommander.ps1 b/Functions/Start-XADCommander.ps1 index 7d45a45..6755d1d 100644 --- a/Functions/Start-XADCommander.ps1 +++ b/Functions/Start-XADCommander.ps1 @@ -1,144 +1,156 @@ function Start-XADCommander { -[cmdletbinding()] -param() -$ExistingADDrive = $CurrentDriveName = '' + [CmdletBinding(SupportsShouldProcess = $true)] + param() + begin {} + process { + if ($pscmdlet.ShouldProcess("computer")) { + $ExistingADDrive = $CurrentDriveName = '' -$ParentFolder = Split-Path $PSScriptRoot -$DataFolder =Join-Path $ParentFolder 'Data' -$Domain_Controllers_IPs_CSV = Import-Csv $Script:DCIPsCSVPath -$Level_2_Menus_CSV = Import-Csv "$DataFolder\Level_2_Menus.csv" -$AllIPs = $Domain_Controllers_IPs_CSV | ForEach-Object { $_.IP } -# Declare $UsedADDrives as a hash table -$UsedADDrives = @{} -# Detect existing AD drives created from a previous session to track them for reuse and clean-up (removal) -# check current PSDrives for drives of type Microsoft.ActiveDirectory.Management.dll\ActiveDirectory and server ip in the DC IPs CSV and add them to the UsedADDrives + $ParentFolder = Split-Path $PSScriptRoot + $DataFolder = Join-Path $ParentFolder 'Data' + $Domain_Controllers_IPs_CSV = Import-Csv $Script:DCIPsCSVPath + $Level_2_Menus_CSV = Import-Csv "$DataFolder\Level_2_Menus.csv" + $AllIPs = $Domain_Controllers_IPs_CSV | ForEach-Object { $_.IP } + # Declare $UsedADDrives as a hash table + $UsedADDrives = @{} + # Detect existing AD drives created from a previous session to track them for reuse and clean-up (removal) + # check current PSDrives for drives of type Microsoft.ActiveDirectory.Management.dll\ActiveDirectory and server ip in the DC IPs CSV and add them to the UsedADDrives -# store existing AD drive names that match the domain controller and IPs in a hashtable -Get-PSDrive | - Where-Object { $_.Provider -match 'ActiveDirectory' -and $_.Server -in $AllIPs -and ($_.Name -eq $_.RootWithoutAbsolutePathToken.Split(',')[0].Substring(3)) } | - Select-Object Name, Server | ForEach-Object { $UsedADDrives[$_.Name] = $_.Server } + # store existing AD drive names that match the domain controller and IPs in a hashtable + Get-PSDrive | + Where-Object { $_.Provider -match 'ActiveDirectory' -and $_.Server -in $AllIPs -and ($_.Name -eq $_.RootWithoutAbsolutePathToken.Split(',')[0].Substring(3)) } | + Select-Object Name, Server | ForEach-Object { $UsedADDrives[$_.Name] = $_.Server } -$CurrentDriveName = (Get-Location).Drive.Name -if (($UsedADDrives.Count -gt 0) -and $UsedADDrives.ContainsKey($CurrentDriveName)) { - Set-location $ENV:USERPROFILE -} -Push-Location -$DomainControllerIP = [ordered]@{} -$Domain_Controllers_IPs_CSV | ForEach-Object { $DomainControllerIP[$_.Label] = $_.IP } -$Options = [string[]]$DomainControllerIP.keys - -:MainMenuExit -while ($true) { - #Clear-Host -Force - $ADDrive = $Domain = '' - $Option = Show-XADMenu -Title 'Domains' -Choices $Options - if ($Option -eq 0) { - break MainMenuExit - } - $SelectedLabel = $Options[$Option - 1] - # Determine if we can use an existing AD drive - $Server = $DomainControllerIP.$SelectedLabel - write-Verbose "SelectedLabel: $SelectedLabel, Corresponding Server: $Server" - if ($Server -in $UsedADDrives.Values) { - foreach ($EnumUsedADDrives in $UsedADDrives.GetEnumerator()) { - if ($EnumUsedADDrives.Value -eq $Server) { - $ExistingADDrive = $EnumUsedADDrives.Key - Write-Verbose "Found existing AD drive $ExistingADDrive for server $Server" - } - } - # Check if existing AD drive authentication with the domain is still valid - if (Test-XADDrive -Name $ExistingADDrive) { - $Domain = $ExistingADDrive - $ADDrive = "$($Domain):" - } - } - if (-not $ADDrive) { - Write-Host "`nConnecting to domain controller $Server in $SelectedLabel.............." -ForegroundColor Yellow - $Credential = Get-Credential -Message "Enter credential for the domain in $SelectedLabel" - :CreateADDrive - do { - try { - $Domain = New-XADDrive -DomainControllers $Server -Credential $Credential -NoConnectionTest -ErrorAction Stop | Select-Object -ExpandProperty Name + $CurrentDriveName = (Get-Location).Drive.Name + if (($UsedADDrives.Count -gt 0) -and $UsedADDrives.ContainsKey($CurrentDriveName)) { + Set-Location $ENV:USERPROFILE } - catch { - $ErrorRecord = $_ - switch ($ErrorRecord.FullyQualifiedErrorId) { - # Ensure new AD drive name is not already taken - 'DriveAlreadyExists,New-XADDrive' { - "A drive with name $($ErrorRecord.TargetObject) already exists. You'll be prompted to confirm deleting the $($ErrorRecord.TargetObject) drive." - $Confirm = Read-Host -Prompt "`nType 'y' or 'Y' if you confirm deleting drive $($ErrorRecord.TargetObject) or type anything else to keep it and to return to the domain selection menu again" - if ($Confirm -notin 'y', 'Y') { - continue MainMenuExit - } - else { - if ((Get-Location -Stack).Path[0].StartsWith($ErrorRecord.TargetObject)){ - Pop-Location - Set-location $ENV:USERPROFILE - Push-Location - } - Remove-PSDrive -Name $ErrorRecord.TargetObject -Force - continue CreateADDrive + Push-Location + $DomainControllerIP = [ordered]@{} + $Domain_Controllers_IPs_CSV | ForEach-Object { $DomainControllerIP[$_.Label] = $_.IP } + $Options = [string[]]$DomainControllerIP.keys + + :MainMenuExit + while ($true) { + #Clear-Host -Force + $ADDrive = $Domain = '' + $Option = Show-XADMenu -Title 'Domains' -Choices $Options + if ($Option -eq 0) { + break MainMenuExit + } + $SelectedLabel = $Options[$Option - 1] + # Determine if we can use an existing AD drive + $Server = $DomainControllerIP.$SelectedLabel + Write-Verbose "SelectedLabel: $SelectedLabel, Corresponding Server: $Server" + if ($Server -in $UsedADDrives.Values) { + foreach ($EnumUsedADDrives in $UsedADDrives.GetEnumerator()) { + if ($EnumUsedADDrives.Value -eq $Server) { + $ExistingADDrive = $EnumUsedADDrives.Key + Write-Verbose "Found existing AD drive $ExistingADDrive for server $Server" } } - default{ - $ErrorDetails = $ErrorRecord.Exception.Message - "AD drive creation failed for $($Credential.Username) in $SelectedLabel. ErrorDetails: $ErrorDetails" - $Confirm = Read-Host -Prompt "`nType 'y' or 'Y' if you want to return to the domain selection menu again or type anything else to exit" - if ($Confirm -notin 'y', 'Y') { - break MainMenuExit - } - continue MainMenuExit + # Check if existing AD drive authentication with the domain is still valid + if (Test-XADDrive -Name $ExistingADDrive) { + $Domain = $ExistingADDrive + $ADDrive = "$($Domain):" } } - } - $ADDrive = "$($Domain):" - Write-Verbose "UsedADDrives: $($UsedADDrives.Keys)" - $UsedADDrives[$Domain] = $Server - Write-Verbose "NewADDrive: $Domain" - } until( $ADDrive ) - } - Write-Verbose "Switching to $Domain" - Set-Location "$($ADDrive)\" + if (-not $ADDrive) { + Write-Host "`nConnecting to domain controller $Server in $SelectedLabel.............." -ForegroundColor Yellow + $Credential = Get-Credential -Message "Enter credential for the domain in $SelectedLabel" + :CreateADDrive + do { + try { + $Domain = New-XADDrive -DomainControllers $Server -Credential $Credential -NoConnectionTest -ErrorAction Stop | Select-Object -ExpandProperty Name + } + catch { + $ErrorRecord = $_ + switch ($ErrorRecord.FullyQualifiedErrorId) { + # Ensure new AD drive name is not already taken + 'DriveAlreadyExists,New-XADDrive' { + "A drive with name $($ErrorRecord.TargetObject) already exists. You'll be prompted to confirm deleting the $($ErrorRecord.TargetObject) drive." + $Confirm = Read-Host -Prompt "`nType 'y' or 'Y' if you confirm deleting drive $($ErrorRecord.TargetObject) or type anything else to keep it and to return to the domain selection menu again" + if ($Confirm -notin 'y', 'Y') { + continue MainMenuExit + } + else { + if ((Get-Location -Stack).Path[0].StartsWith($ErrorRecord.TargetObject)) { + Pop-Location + Set-Location $ENV:USERPROFILE + Push-Location + } + Remove-PSDrive -Name $ErrorRecord.TargetObject -Force + continue CreateADDrive + } + } + default { + $ErrorDetails = $ErrorRecord.Exception.Message + "AD drive creation failed for $($Credential.Username) in $SelectedLabel. ErrorDetails: $ErrorDetails" + $Confirm = Read-Host -Prompt "`nType 'y' or 'Y' if you want to return to the domain selection menu again or type anything else to exit" + if ($Confirm -notin 'y', 'Y') { + break MainMenuExit + } + continue MainMenuExit + } + } + } + $ADDrive = "$($Domain):" + Write-Verbose "UsedADDrives: $($UsedADDrives.Keys)" + $UsedADDrives[$Domain] = $Server + Write-Verbose "NewADDrive: $Domain" + } until( $ADDrive ) + } + Write-Verbose "Switching to $Domain" + Set-Location "$($ADDrive)\" - $Level_2_Menus = [ordered]@{} - $Level_2_Menus_CSV | ForEach-Object { $Level_2_Menus[$_.Menu_ID] = $_.Menu_Name } - $Actions = [string[]]$Level_2_Menus.Values - :SubMenuExit - while ($true) { - #Clear-host -Force - $SelectedMenuID = Show-XADMenu -Title "Actions for domain: $Domain" -Choices $Actions - if ($SelectedMenuID -eq 0) { break MainMenuExit } - $SelectedMenu = $Level_2_Menus[$SelectedMenuID - 1] - do { - switch ($SelectedMenuID) { - 1 { Reset-XADUserPassword $Domain} - 2 { New-XADAdmin $Domain} - 3 { Add-XADGroupMember $Domain} - 4 { New-XADServiceAccount $Domain} - 5 { break SubMenuExit } - 6 { $UsedADDrives.Remove($Domain) | Out-Null; break MainMenuExit} - default { Write-Warning "Unknown Option: $SelectedMenuID" } - } - # If the AD drive was found invalid in any called function above, start over from domains menu - if ( -not (Test-XADDrive $Domain) ) { - Write-Host "Connection with the domain $Domain is no longer valid, you'll be taken to domains menu to start over again" -ForegroundColor Red - Set-Location $Env:USERPROFILE - Remove-PSDrive -Name $Domain -Force - $UsedADDrives.Remove($Domain) - break SubMenuExit + $Level_2_Menus = [ordered]@{} + $Level_2_Menus_CSV | ForEach-Object { $Level_2_Menus[$_.Menu_ID] = $_.Menu_Name } + $Actions = [string[]]$Level_2_Menus.Values + :SubMenuExit + while ($true) { + #Clear-host -Force + $SelectedMenuID = Show-XADMenu -Title "Actions for domain: $Domain" -Choices $Actions + if ($SelectedMenuID -eq 0) { break MainMenuExit } + $SelectedMenu = $Level_2_Menus[$SelectedMenuID - 1] + do { + switch ($SelectedMenuID) { + 1 { Reset-XADUserPassword $Domain } + 2 { New-XADAdmin $Domain } + 3 { Add-XADGroupMember $Domain } + 4 { New-XADServiceAccount $Domain } + 5 { break SubMenuExit } + 6 { $UsedADDrives.Remove($Domain) | Out-Null; break MainMenuExit } + default { Write-Warning "Unknown Option: $SelectedMenuID" } + } + # If the AD drive was found invalid in any called function above, start over from domains menu + if ( -not (Test-XADDrive $Domain) ) { + Write-Host "Connection with the domain $Domain is no longer valid, you'll be taken to domains menu to start over again" -ForegroundColor Red + Set-Location $Env:USERPROFILE + Remove-PSDrive -Name $Domain -Force + $UsedADDrives.Remove($Domain) + break SubMenuExit + } + } until ( + ((Read-Host -Prompt "`nType 'y' or 'Y' if you want to use `"$SelectedMenu`" again in $Domain or type anything else to go back to Actions menu") -notin 'y', 'Y') + ) + } } - } until ( - ((read-host -Prompt "`nType 'y' or 'Y' if you want to use `"$SelectedMenu`" again in $Domain or type anything else to go back to Actions menu") -notin 'y', 'Y') - ) + # Cleanup + if ( $SelectedMenuID -ne 6 ) { Pop-Location } + Write-Verbose "Removing AD drives used: $($UsedADDrives.keys)" + # Remove all previously used AD drives + $UsedADDrives.Keys | + ForEach-Object { + Remove-PSDrive $_ -ErrorAction Continue + } + return + } } + end {} } -# Cleanup -if ( $SelectedMenuID -ne 6 ) {Pop-Location} -Write-Verbose "Removing AD drives used: $($UsedADDrives.keys)" -# Remove all previously used AD drives -$UsedADDrives.Keys | - ForEach-Object { - Remove-PSDrive $_ -ErrorAction Continue - } -Return -} \ No newline at end of file + + + + + + diff --git a/Functions/Test-XADDrive.ps1 b/Functions/Test-XADDrive.ps1 index 3e6e39c..fb7d53a 100644 --- a/Functions/Test-XADDrive.ps1 +++ b/Functions/Test-XADDrive.ps1 @@ -34,7 +34,7 @@ function Test-XADDrive { [string]$Name ) - PROCESS { + process { try { Write-Verbose "Checking if AD drive $Name exists" Get-PSDrive -Name $Name -PSProvider ActiveDirectory -ErrorAction Stop | Out-Null @@ -50,3 +50,9 @@ function Test-XADDrive { } } } + + + + + + diff --git a/Internal/Initialize-XADConfig.ps1 b/Internal/Initialize-XADConfig.ps1 index 1e8f7be..b1678ab 100644 --- a/Internal/Initialize-XADConfig.ps1 +++ b/Internal/Initialize-XADConfig.ps1 @@ -5,7 +5,12 @@ function Initialize-XADConfig { New-Item -ItemType Directory -Path $script:ModuleDataPath -Force | Out-Null } if (-not (Test-Path $script:DCIPsCSVPath)) { - $Entries = "Domain,IP","Domain_1,172.28.167.74","Domain_2,172.28.167.50","Domain_3,172.28.167.200" - $Entries | ForEach-Object {$_ | Out-File -FilePath $script:DCIPsCSVPath -Encoding utf8 -Append} + $Entries = "Domain,IP", "Domain_1,172.28.167.74", "Domain_2,172.28.167.50", "Domain_3,172.28.167.200" + $Entries | ForEach-Object { $_ | Out-File -FilePath $script:DCIPsCSVPath -Encoding utf8 -Append } } -} \ No newline at end of file +} + + + + + diff --git a/Internal/Show-WelcomeBanner.ps1 b/Internal/Show-WelcomeBanner.ps1 index 5aadbd4..f7301ac 100644 --- a/Internal/Show-WelcomeBanner.ps1 +++ b/Internal/Show-WelcomeBanner.ps1 @@ -26,3 +26,9 @@ function Show-WelcomeBanner { } + + + + + + diff --git a/Internal/Show-XADMenu.ps1 b/Internal/Show-XADMenu.ps1 index f9b853a..690896f 100644 --- a/Internal/Show-XADMenu.ps1 +++ b/Internal/Show-XADMenu.ps1 @@ -53,10 +53,15 @@ function Show-XADMenu { catch { $SelectedChoice = 0 $SelectedChoice - Return + return } - if ($SelectedChoice -lt 1 -or $SelectedChoice -gt $Choices.Count){ + if ($SelectedChoice -lt 1 -or $SelectedChoice -gt $Choices.Count) { $SelectedChoice = 0 } $SelectedChoice -} \ No newline at end of file +} + + + + + diff --git a/Internal/Wait-ForKeyPress.ps1 b/Internal/Wait-ForKeyPress.ps1 index 19732f2..51d1b1a 100644 --- a/Internal/Wait-ForKeyPress.ps1 +++ b/Internal/Wait-ForKeyPress.ps1 @@ -9,3 +9,9 @@ function Wait-AnyKeyPress { $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } + + + + + + diff --git a/Tests/Unit/Test-XADDrive.Tests.ps1 b/Tests/Unit/Test-XADDrive.Tests.ps1 index b19c01f..844dda2 100644 --- a/Tests/Unit/Test-XADDrive.Tests.ps1 +++ b/Tests/Unit/Test-XADDrive.Tests.ps1 @@ -1,5 +1,5 @@ BeforeAll { - . $PSCommandPath.Replace('.Tests.ps1','.ps1') + . $PSCommandPath.Replace('.Tests.ps1', '.ps1') . $PSScriptRoot\New-XADDrive.ps1 } Describe "Test-XADDrive" { @@ -23,4 +23,9 @@ Describe "Test-XADDrive" { } } -} \ No newline at end of file +} + + + + + diff --git a/X-ADCommander.psd1 b/X-ADCommander.psd1 index fecc457..a0a7cde 100644 --- a/X-ADCommander.psd1 +++ b/X-ADCommander.psd1 @@ -8,125 +8,126 @@ @{ -# Script module or binary module file associated with this manifest. -RootModule = 'X-ADCommander.psm1' + # Script module or binary module file associated with this manifest. + RootModule = 'X-ADCommander.psm1' -# Version number of this module. -ModuleVersion = '1.0.6' + # Version number of this module. + ModuleVersion = '1.0.6' -# Supported PSEditions -CompatiblePSEditions = 'Desktop', 'Core' + # Supported PSEditions + CompatiblePSEditions = 'Desktop', 'Core' -# ID used to uniquely identify this module -GUID = '10319cd3-5256-4678-8d98-89b38050bd00' + # ID used to uniquely identify this module + GUID = '10319cd3-5256-4678-8d98-89b38050bd00' -# Author of this module -Author = 'Mhd Samer Sawas' + # Author of this module + Author = 'Mhd Samer Sawas' -# Company or vendor of this module -CompanyName = 'Mhd Samer Sawas' + # Company or vendor of this module + CompanyName = 'Mhd Samer Sawas' -# Copyright statement for this module -Copyright = '(c) 2025 Mhd Samer Sawas. All rights reserved.' + # Copyright statement for this module + Copyright = '(c) 2025 Mhd Samer Sawas. All rights reserved.' -# Description of the functionality provided by this module -Description = 'Cross-forest AD administration made simple, scriptable, and extensible.' + # Description of the functionality provided by this module + Description = 'PowerShell-based, extensible management framework designed to make cross-forest Active Directory administration simple, scriptable, and efficient. It provides an interactive text-menu console that lets administrators browse and manage multiple AD forests and domains from one unified interface. The module also exposes core functions (like New-XADDrive and Test-XADDrive) that can be used directly in custom scripts, and it supports easy extension with new management tasks.' -# Minimum version of the PowerShell engine required by this module -PowerShellVersion = '5.1' + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.1' -# Name of the PowerShell host required by this module -# PowerShellHostName = '' + # Name of the PowerShell host required by this module + # PowerShellHostName = '' -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = 'New-XADDrive', 'Test-XADDrive', 'Start-XADCommander' + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = 'New-XADDrive', 'Test-XADDrive', 'Start-XADCommander' -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() -# Variables to export from this module -# VariablesToExport = @() + # Variables to export from this module + # VariablesToExport = @() -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = @() + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() -# DSC resources to export from this module -# DscResourcesToExport = @() + # DSC resources to export from this module + # DscResourcesToExport = @() -# List of all modules packaged with this module -# ModuleList = @() + # List of all modules packaged with this module + # ModuleList = @() -# List of all files packaged with this module -# FileList = @() + # List of all files packaged with this module + # FileList = @() -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ - PSData = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = 'activedirectory','domain','multi-domain','multi-forest','cross-forest','management','console','interactive','text-menu','cross-forest','administration','xadcommander','x-adcommander','PowerShell','extensible','Powershell-5.1+','PowerShell-Core','PowerShell-7+' + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'activedirectory', 'domain', 'multi-domain', 'multi-forest', 'cross-forest', 'management', 'console', 'interactive', 'text-menu', 'cross-forest', 'administration', 'xadcommander', 'x-adcommander', 'PowerShell', 'extensible', 'Powershell-5.1+', 'PowerShell-Core', 'PowerShell-7+' - # A URL to the license for this module. - # LicenseUri = '' + # A URL to the license for this module. + # LicenseUri = '' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/msamersawas/X-ADCommander' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/msamersawas/X-ADCommander' - # A URL to an icon representing this module. - # IconUri = '' + # A URL to an icon representing this module. + # IconUri = '' - # ReleaseNotes of this module - ReleaseNotes = '- Returned Write-Host for console menus and coloring.' + # ReleaseNotes of this module + ReleaseNotes = '- Returned Write-Host for console menus and coloring.' - # Prerelease string of this module - # Prerelease = '' + # Prerelease string of this module + # Prerelease = '' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - ExternalModuleDependencies = @('ActiveDirectory') + # External dependent modules of this module + ExternalModuleDependencies = @('ActiveDirectory') - } # End of PSData hashtable + } # End of PSData hashtable - } # End of PrivateData hashtable + } # End of PrivateData hashtable -# HelpInfo URI of this module -# HelpInfoURI = '' + # HelpInfo URI of this module + # HelpInfoURI = '' -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' } + diff --git a/X-ADCommander.psm1 b/X-ADCommander.psm1 index b5cac3f..69a1cc3 100644 --- a/X-ADCommander.psm1 +++ b/X-ADCommander.psm1 @@ -1,6 +1,6 @@ # Declare module-wide variables here $script:ModuleDataPath = Join-Path $env:LocalAppData "X-ADCommander" -$script:DCIPsCSVPath = Join-Path $script:ModuleDataPath "Domain_Controllers_IPs.csv" +$script:DCIPsCSVPath = Join-Path $script:ModuleDataPath "Domain_Controllers_IPs.csv" # Load all function scripts from all folders containing functions when the module is imported @@ -19,4 +19,4 @@ Initialize-XADConfig if ($Host.Name -match "ConsoleHost|Visual Studio Code Host") { Show-WelcomeBanner -} \ No newline at end of file +}