diff --git a/CHANGELOG.md b/CHANGELOG.md index d1da8bf..51be400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to IDBridge are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions use a calendar scheme `YY.M.D.build` (see [CONTRIBUTING.md](CONTRIBUTING.md#versioning--releases)). +## [26.9.1.3] - 2026-09-01 + +### Changed +- **`Reset-IDBridgeADPassword` CSVs now lead with `FirstName` and `LastName` columns** + (from AD GivenName/Surname), ahead of SamAccountName/Passphrase/OrgUnit — both the + reset run's handout CSV and the export-only CSV. + ## [26.9.1.2] - 2026-09-01 ### Changed diff --git a/docs/functions.md b/docs/functions.md index b2d58c2..b1756e7 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -291,7 +291,7 @@ ChangePasswordAtLogon, passphrase-CSV export — off by default). "Load Users" l accounts for review; "Reset Passwords" confirms with the exact count, fetches every passphrase up front (`Get-ADUsersToResetPassword` — any Keysmith failure aborts before a single write), then applies `Set-ADAccountPassword -Reset` per account. "Export Passphrases" instead writes -the loaded users' regenerated passphrases as a username/passphrase/OU CSV to `Exports` +the loaded users' regenerated passphrases as a name/username/passphrase/OU CSV to `Exports` **without touching AD** (matches current passwords only when nonce/mode/word count/rev match what they were last set with — pin the rev for older accounts). Writes to AD when confirmed **regardless of `Debug.readOnly`** — interactive, gated by its own confirmation dialog. @@ -523,7 +523,7 @@ deterministic passphrase per SamAccountName (one batched call per 500 users, the per-request max; phrases pair with users by position) and builds a `Set-ADAccountPassword -Reset` splat each. An API failure or returned-count mismatch throws — no partial reset list. The plaintext phrase rides on each item for the caller's optional export and is never logged. -**Returns:** `@{ SamAccountName; DistinguishedName; Passphrase; Splat }[]`. +**Returns:** `@{ SamAccountName; GivenName; Surname; DistinguishedName; Passphrase; Splat }[]`. ### `Get-ADUserGroupsToUpdate` 🔒 🧮 **Params:** `-UserList`, `-CurrentADGroups`. Diffs `GroupsProposed` vs `ADCurrentGroups` diff --git a/src/IDBridge/IDBridge.psd1 b/src/IDBridge/IDBridge.psd1 index b8dc7c1..03cb3c4 100644 --- a/src/IDBridge/IDBridge.psd1 +++ b/src/IDBridge/IDBridge.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'IDBridge.psm1' - ModuleVersion = '26.9.1.2' + ModuleVersion = '26.9.1.3' GUID = 'a0a0c664-888e-44e2-9c12-fc8647a520c0' Author = 'Sam Cattanach' CompanyName = 'SC Networks LLC' diff --git a/src/IDBridge/Private/AD/Get-ADUsersToResetPassword.ps1 b/src/IDBridge/Private/AD/Get-ADUsersToResetPassword.ps1 index a1057a8..04ac989 100644 --- a/src/IDBridge/Private/AD/Get-ADUsersToResetPassword.ps1 +++ b/src/IDBridge/Private/AD/Get-ADUsersToResetPassword.ps1 @@ -23,8 +23,8 @@ The caller is only exporting the passphrases, not resetting — the per-user pro "Export passphrase" instead of "Reset password". Output is identical either way. .OUTPUTS -[object[]] of @{ SamAccountName; DistinguishedName; Passphrase; Splat } where Splat is the -Set-ADAccountPassword parameter hashtable. +[object[]] of @{ SamAccountName; GivenName; Surname; DistinguishedName; Passphrase; Splat } +where Splat is the Set-ADAccountPassword parameter hashtable. .EXAMPLE $resets = Get-ADUsersToResetPassword -UserList $users -PassphraseAPI $api @@ -86,6 +86,8 @@ function Get-ADUsersToResetPassword { $itemList += [PSCustomObject]@{ SamAccountName = $item.SamAccountName + GivenName = $item.GivenName + Surname = $item.Surname DistinguishedName = $item.DistinguishedName Passphrase = $phrases[$i] Splat = @{ diff --git a/src/IDBridge/Public/Core/Reset-IDBridgeADPassword.ps1 b/src/IDBridge/Public/Core/Reset-IDBridgeADPassword.ps1 index b212374..4f907f4 100644 --- a/src/IDBridge/Public/Core/Reset-IDBridgeADPassword.ps1 +++ b/src/IDBridge/Public/Core/Reset-IDBridgeADPassword.ps1 @@ -15,7 +15,7 @@ account, optionally also setting ChangePasswordAtLogon. Because Keysmith passphrases are deterministic, the phrases never need to be stored — anyone with the nonce can regenerate them at keysmith.scnlabs.net. Optionally the run can export a -username/passphrase/OU CSV to the Exports folder for handout (off by default). "Export +name/username/passphrase/OU CSV to the Exports folder for handout (off by default). "Export Passphrases" instead writes that same CSV for the loaded users WITHOUT touching AD — regenerating what the current passwords already are, e.g. for login slips. The exported phrases match the accounts' current passwords only when the nonce, mode, word count, and @@ -520,7 +520,7 @@ function Reset-IDBridgeADPassword { if ($chkExport.Checked -and $succeededItems.Count -gt 0) { try { $state.ExportPath = Join-Path $IDConfig.Paths.ExportsRoot ("ADPasswordReset_" + (Get-Date -Format "yyyy-MM-dd-HH.mm.ss") + ".csv") - $succeededItems | Select-Object SamAccountName, Passphrase, @{n = 'OrgUnit'; e = { [regex]::Match(($_.DistinguishedName -replace '^CN=(?:\\.|[^,\\])*,', ''), '^OU=((?:\\.|[^,\\])*)').Groups[1].Value -replace '\\(.)', '$1' }} | Export-Csv -Path $state.ExportPath -NoTypeInformation + $succeededItems | Select-Object @{n = 'FirstName'; e = { $_.GivenName }}, @{n = 'LastName'; e = { $_.Surname }}, SamAccountName, Passphrase, @{n = 'OrgUnit'; e = { [regex]::Match(($_.DistinguishedName -replace '^CN=(?:\\.|[^,\\])*,', ''), '^OU=((?:\\.|[^,\\])*)').Groups[1].Value -replace '\\(.)', '$1' }} | Export-Csv -Path $state.ExportPath -NoTypeInformation Write-Log -Message "Reset: Exported $($succeededItems.Count) passphrase(s) to $($state.ExportPath). Delete the file after handout." } catch { @@ -564,7 +564,7 @@ function Reset-IDBridgeADPassword { try { $state.ExportPath = Join-Path $IDConfig.Paths.ExportsRoot ("ADPasswordExport_" + (Get-Date -Format "yyyy-MM-dd-HH.mm.ss") + ".csv") - $resets | Select-Object SamAccountName, Passphrase, @{n = 'OrgUnit'; e = { [regex]::Match(($_.DistinguishedName -replace '^CN=(?:\\.|[^,\\])*,', ''), '^OU=((?:\\.|[^,\\])*)').Groups[1].Value -replace '\\(.)', '$1' }} | Export-Csv -Path $state.ExportPath -NoTypeInformation + $resets | Select-Object @{n = 'FirstName'; e = { $_.GivenName }}, @{n = 'LastName'; e = { $_.Surname }}, SamAccountName, Passphrase, @{n = 'OrgUnit'; e = { [regex]::Match(($_.DistinguishedName -replace '^CN=(?:\\.|[^,\\])*,', ''), '^OU=((?:\\.|[^,\\])*)').Groups[1].Value -replace '\\(.)', '$1' }} | Export-Csv -Path $state.ExportPath -NoTypeInformation } catch { Write-Log -Message ("Reset: Passphrase export failed: $($_.Exception.Message)") -Level Error diff --git a/tests/Private/AD/Get-ADUsersToResetPassword.Tests.ps1 b/tests/Private/AD/Get-ADUsersToResetPassword.Tests.ps1 index de6e69f..2f623d3 100644 --- a/tests/Private/AD/Get-ADUsersToResetPassword.Tests.ps1 +++ b/tests/Private/AD/Get-ADUsersToResetPassword.Tests.ps1 @@ -35,6 +35,8 @@ Describe 'Get-ADUsersToResetPassword' { $result.Count | Should -Be 2 $result[0].SamAccountName | Should -Be 'alice' + $result[0].GivenName | Should -Be 'Test' + $result[0].Surname | Should -Be 'User' $result[0].Passphrase | Should -Be 'phrase-alice' $result[1].Passphrase | Should -Be 'phrase-bob'