function writeHeaders { ################################################################ # -------------------------------------------------------------- # Header für $DAWGruppenAnlegenOutFilePath anlegen # -------------------------------------------------------------- "Basket.Import.ADGroup.Create" | Out-File $DAWGruppenAnlegenOutFilePath -Encoding "utf8NoBOM" "GUIMandator" + ";" + "GUIADGroup.Name" + ";" + "GUIADGroup.Description" + ";" + "GUIMandatorADGroupType" + ";" + "GUIADGroup.DisplayName" + ";" + "GUIProcessingNote" + ";" + "OldName" | Out-File $DAWGruppenAnlegenOutFilePath -Append -Encoding "utf8NoBom" # -------------------------------------------------------------- # Header für $DAWGruppenverwaltungOutFilePath anlegen # -------------------------------------------------------------- "Basket.Import.ADGroup.ManageMembers" | Out-File "$DAWGruppenverwaltungOutFilePath" -Encoding "utf8NoBom" "GUIMandator" + ";" + "GUIMandatorADGroupType" + ";" + "GUIADGroup.LDAP" + ";" + "GUIADGroup.ADGroups.Add.LDAPs" + ";" + "GUIADGroup.ADGroups.Rem.LDAPs" + ";" + "GUIADGroup.Accounts.Add.LDAPs" + ";" + "GUIADGroup.Accounts.Rem.LDAPs" + ";" + "GUIProcessingNote" + ";" + "ADObjectType" + ";" + "Quellgruppe" | Out-File $DAWGruppenverwaltungOutFilePath -Encoding "utf8NoBom" -Append "Basket.Import.ADGroup.ManageMembers" | Out-File "$DAWGruppenverwaltungNeuOutFilePath" -Encoding "utf8NoBom" "GUIMandator" + ";" + "GUIMandatorADGroupType" + ";" + "GUIADGroup.LDAP" + ";" + "GUIADGroup.ADGroups.Add.LDAPs" + ";" + "GUIADGroup.ADGroups.Rem.LDAPs" + ";" + "GUIADGroup.Accounts.Add.LDAPs" + ";" + "GUIADGroup.Accounts.Rem.LDAPs" + ";" + "GUIProcessingNote" + ";" + "ADObjectType" + ";" + "Quellgruppe" | Out-File $DAWGruppenverwaltungNeuOutFilePath -Encoding "utf8NoBom" -Append # -------------------------------------------------------------- # Header für $KURSSollProfile anlegen # -------------------------------------------------------------- "Profilnummer;Name;Beschreibung;Profilverantwortliche OE/Stelle;Info;InfoKurs" | Out-File $KURSSollProfileOutFilePath -Encoding "utf8NoBom" # -------------------------------------------------------------- # Header für $IstMitarbeiterProfile anlegen # -------------------------------------------------------------- "Personalnummer;;Profilnummer;Name Berechtigungsprofil" | Out-File "$KURSIstMitarbeiterProfileOutFilePath" -Encoding "utf8NoBom" ################################################################ } function getObjectMemberships { param ( [Parameter(Mandatory = $true, Position = 0)] [string] $adObjectEntryName, [Parameter(Mandatory = $true, Position = 1)] [string] $memberShipType, [Parameter(Mandatory = $true, Position = 2)] [string] $memberShipOriginalType ) $key = getMembershipIndexKey $adObjectEntryName $memberShipType $bucket = $ADObjectMembershipsIndexed[$key] $objectMemberships = if ($null -eq $bucket) { @() } else { @($bucket) } $objectMemberships | Add-Member -Force "Origin" $memberShipOriginalType $objectMemberships | Add-Member -Force "OriginName" $adObjectEntryName return $objectMemberships } function getAllUserMemberships { param ( [Parameter(Mandatory = $true, Position = 0)] $adObjectEntry ) $result = [System.Collections.Generic.List[object]]::new() $result.AddRange(@(getObjectMemberships $adObjectEntry.ObjectName "user" "user")) $groupMemberships = @(getObjectMemberships $adObjectEntry.ObjectName "group" "group") foreach ($groupMembership in $groupMemberships) { $result.AddRange(@(getObjectMemberships $groupMembership.MemberName "user" "group")) } # it's the same, bad odd syntax # return ,$result.ToArray() return @($result) } function getUsersByMemberGroup { <# .SYNOPSIS Retrieves the user membership entries for a given group via the prebuilt membership index (O(1) lookup, exact class match). .PARAMETER GroupName The group whose user members are requested (matched against GroupName). .OUTPUTS Array of matching membership entries (empty array if none). Object references are preserved from the index. #> param ( [Parameter(Mandatory = $true, Position = 0)] [string] $GroupName ) # BUGFIX 2026-07-02: was indexing the flat array $ADObjectMemberships with a string # key, which fails silently (non-terminating Int32 conversion error -> $null) and # dropped ALL indirect (group-inherited) user memberships. Must use the hashtable. $bucket = $ADObjectMembershipsIndexed[(getMembershipIndexKey $GroupName "user")] $result = if ($null -eq $bucket) { @() } else { @($bucket) } return $result } # function getAllUserMemberships # { # param([Parameter(Mandatory, Position = 0)] $adObjectEntry) # $result = [System.Collections.Generic.List[object]]::new() # $result.AddRange(@(getObjectMemberships $adObjectEntry.ObjectName "user" "user")) # $groupMemberships = @(getObjectMemberships $adObjectEntry.ObjectName "group" "group") # foreach ($groupMembership in $groupMemberships) # { # $result.AddRange(@(getObjectMemberships $groupMembership.MemberName "user" "group")) # } # # ist beides gleich # # return ,$result.ToArray() # return @($result) # } # function getObjectMemberships # { # param # ( # [Parameter(Mandatory = $true, Position = 0)] # [String] $adObjectEntryName, # [Parameter(Mandatory = $true, Position = 1)] # [string] $memberShipType, # [Parameter(Mandatory = $true, Position = 2)] # [string] $memberShipOriginalType # ) # $objectMemberships = @($ADObjectMemberships | Where-Object {($_.GroupName -ieq $adObjectEntryName) -and ($_.MemberClass -ieq $memberShipType)}) # # this is not very nice - we should only add members if they don´t exist - not just to set the values # $objectMemberships | Add-Member -Force "Origin" $memberShipOriginalType # $objectMemberships | Add-Member -Force "OriginName" $adObjectEntryName # return $objectMemberships # } function getMembershipIndexKey { param ( [Parameter(Mandatory = $true, Position = 0)] [string] $GroupName, [Parameter(Mandatory = $true, Position = 1)] [string] $MemberClass ) return "$GroupName`0$MemberClass".ToLowerInvariant() } function newObjectMembershipsIndex { <# .SYNOPSIS Builds a lookup index over membership entries for O(1) retrieval by GroupName + MemberClass, replacing repeated linear Where-Object scans. .PARAMETER ADObjectMemberships The flat collection of membership entries to index. .OUTPUTS Hashtable mapping "\0" (lowercased) to a List[object] of the matching entries. Object references are preserved. #> param ( [Parameter(Mandatory = $true, Position = 0)] [AllowEmptyCollection()] $ADObjectMemberships ) $index = @{} foreach ($entry in $ADObjectMemberships) { # NUL separator avoids key collisions between e.g. "ab"+"c" and "a"+"bc" $key = getMembershipIndexKey $($entry.GroupName) $($entry.MemberClass) $key0 = "$($entry.GroupName)`0$($entry.MemberClass)".ToLowerInvariant() if (-not($key -eq $key0)) { throw "this is BAAAD" } $bucket = $index[$key] if ($null -eq $bucket) { $bucket = [System.Collections.Generic.List[object]]::new() $index[$key] = $bucket } $bucket.Add($entry) } return $index } function getRoleType { param ( [Parameter(Mandatory = $true, Position = 0)] $adObjectEntry, [Parameter(Mandatory = $true, Position = 1)] [String] $typeTag ) # $FileSystemAccessEntriesNew = @() $roleType = "unknown" # BUGFIX 2026-07-02: 'Sort-Object -Property "Order"' does not resolve hashtable keys on # Windows PowerShell 5.1 (silent no-op, declaration order preserved). The scriptblock # form works identically on 5.1 and 7, making rule evaluation order engine-independent. $relevantRoleTypes = $SPK.GroupManagementMappings | Where-Object {$_.OU -ieq $typeTag} | Sort-Object { [int]$_.Order } foreach ($relevantRoleType in $relevantRoleTypes) { if ($adObjectEntry.OldName -cmatch $relevantRoleType.TagRegExp) { $roleType = $relevantRoleType.TypeName break } } # Ausnahmebehandlungen in Absprache mit N.Zimmer am 21.01.2019 #-------------------------------------------------------------- if (($typeTag -ieq $FI.TypeTagSecurity) -and ($roleType -ieq "unknown")) { $roleType = "GPO" } #-------------------------------------------------------------- return $roletype } function buildPrefixMatcher { param( [Parameter(Mandatory = $true, Position = 0)] [string]$CsvPath ) if (-not (Test-Path $CsvPath)) { throw "Prefix CSV not found: $CsvPath" } # length -> HashSet[string] of prefixes with exactly that length $byLength = [System.Collections.Generic.Dictionary[int, System.Collections.Generic.HashSet[string]]]::new() # foreach ($row in (Import-Csv -Path $CsvPath)) foreach ($row in $SPK.ProfilPrefixes) { $prefix = $row.Prefix if ([string]::IsNullOrEmpty($prefix)) { continue } $len = $prefix.Length $bucket = $null if (-not $byLength.TryGetValue($len, [ref] $bucket)) { $bucket = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) $byLength[$len] = $bucket } [void]$bucket.Add($prefix) } # Pre-compute the distinct lengths once, sorted descending so the most specific # (longest) prefix wins first - mirrors the "specific before generic" ordering # of a well-formed elseif cascade. $lengths = $byLength.Keys | Sort-Object -Descending # Return a small state object carrying both structures. return [pscustomobject]@{ ByLength = $byLength Lengths = $lengths } } function testKnownPrefixes { param( [Parameter(Mandatory = $true, Position = 0)] [string]$Value, [Parameter(Mandatory = $true, Position = 1)] $Matcher ) if ([string]::IsNullOrEmpty($Value)) { return $false } $valueLen = $Value.Length foreach ($len in $Matcher.Lengths) { if ($len -gt $valueLen) { continue } # prefix longer than value -> can't match $candidate = $Value.Substring(0, $len) $bucket = $Matcher.ByLength[$len] if ($bucket.Contains($candidate)) { return $true # found a known prefix } } return $false } # function getNormalizedDirectoryPaths # { # $analyzedDirectoryEntries = @() # # $header = "Verzeichnisname", "Endpunktname", "Gruppe Lesen", "Gruppe Aendern", "Benutzer mit Leserecht", "Benutzer mit Aenderungsrecht", "Rollen mit Leserecht", "Rollen mit Aenderungsrecht", "Verzeichnisart", "normierter Pfad" # # $directoriesFilePathEntries = Get-Content -Path $DirectoriesFilePath | Select-Object -Skip 1 | ConvertFrom-Csv -Delimiter ";" -Header $header # # $directoriesFilePathEntries = Get-Content -Path $DirectoriesInFilePath | Select-Object -Skip 1 | ConvertFrom-Csv -Delimiter ";" # $directoriesFilePathEntries = Get-Content -Path $DirectoriesInFilePath | ConvertFrom-Csv -Delimiter ";" # # $headers = ($directoriesFilePathEntries | Get-Member -MemberType NoteProperty) # # $interstingHeader = $headers.Name -match "Gruppe .ndern" # foreach ($FilePathEntry in $directoriesFilePathEntries) # { # $analyzedDirectoryEntries += $FilePathEntry.Name # } # } function postProcess { param ( [Parameter(Mandatory = $false, Position = 0)] [bool]$convertToAscii = $false ) ######################################################### # just to be sure that nothing happens: comment it out until you know what`s happening here ! ######################################################### # $tmpFilePath = moveToInstituteTmpPath $DAWGruppenAnlegenOutFilePath $true # $firstlines = Get-Content -Path $tmpFilePath -TotalCount 2 # Get-Content -Path $tmpFilePath -TotalCount 1 | Out-File -Force $DAWGruppenAnlegenOutFilePath # Get-Content -Path $tmpFilePath | Select-Object -Skip 2 | ConvertFrom-Csv -Delimiter ";" | select 'GUIMandator', 'GUIADGroup.Name', 'GUIADGroup.Description', 'GUIMandatorADGroupType', 'GUIADGroup.DisplayName', 'GUIProcessingNote' | ConvertTo-Csv -Delimiter ';' -NoTypeInformation | Out-File -FilePath $DAWGruppenAnlegenOutFilePath -Encoding UTF8 -Append # # Import-Csv $tmpFilePath | select 'GUIMandator','GUIADGroup.Name','GUIADGroup.Description','GUIMandatorADGroupType','GUIADGroup.DisplayName','GUIProcessingNote' | Export-Csv -Path $DAWGruppenAnlegenOutFilePath -Delimiter ";" -Encoding UTF8 # $tmpFilePath = moveToInstituteTmpPath $DAWGruppenverwaltungOutFilePath $true # $firstlines = Get-Content -Path $tmpFilePath -TotalCount 2 # Get-Content -Path $tmpFilePath -TotalCount 1 | Out-File -Force $DAWGruppenverwaltungOutFilePath # Get-Content -Path $tmpFilePath | Select-Object -Skip 2 | ConvertFrom-Csv -Delimiter ";" | select "GUIMandator", "GUIMandatorADGroupType", "GUIADGroup.LDAP", "GUIADGroup.ADGroups.Add.LDAPs", "GUIADGroup.ADGroups.Rem.LDAPs", "GUIADGroup.Accounts.Add.LDAPs", "GUIADGroup.Accounts.Rem.LDAPs", "GUIProcessingNote" | ConvertTo-Csv -Delimiter ';' -NoTypeInformation | Out-File -FilePath $DAWGruppenverwaltungOutFilePath -Encoding UTF8 -Append # # Import-Csv $tmpFilePath | select "GUIMandator","GUIMandatorADGroupType","GUIADGroup.LDAP","GUIADGroup.ADGroups.Add.LDAPs","GUIADGroup.ADGroups.Rem.LDAPs","GUIADGroup.Accounts.Add.LDAPs","GUIADGroup.Accounts.Rem.LDAPs","GUIProcessingNote" | Export-Csv -Path $DAWGruppenverwaltungOutFilePath -Delimiter ";" -Encoding UTF8 -NoTypeInformation # $tmpFilePath = moveToInstituteTmpPath $DAWGruppenverwaltungNeuOutFilePath $true # $firstlines = Get-Content -Path $tmpFilePath -TotalCount 2 # Get-Content -Path $tmpFilePath -TotalCount 1 | Out-File -Force $DAWGruppenverwaltungNeuOutFilePath # Get-Content -Path $tmpFilePath | Select-Object -Skip 2 | ConvertFrom-Csv -Delimiter ";" | select "GUIMandator", "GUIMandatorADGroupType", "GUIADGroup.LDAP", "GUIADGroup.ADGroups.Add.LDAPs", "GUIADGroup.ADGroups.Rem.LDAPs", "GUIADGroup.Accounts.Add.LDAPs", "GUIADGroup.Accounts.Rem.LDAPs", "GUIProcessingNote" | ConvertTo-Csv -Delimiter ';' -NoTypeInformation | Out-File -FilePath $DAWGruppenverwaltungNeuOutFilePath -Encoding UTF8 -Append ####################################################### todo ? # $KURSIstProfileOutFilePath # $KURSIstProfileBerechtigungenOutFilePath # $KURSIstStellenBerechtigungenOutFilePath # $KURSIstStellenProfileOutFilePath # $KURSIstOEsBerechtigungenOutFilePath # $KURSIstOEsProfileOutFilePath # $KURSIstMitarbeiterProfileOutFilePath # $KURSSollStellenfunktionenOutFilePath # $KURSSollProfileOutFilePath # $KURSSollProfileBerechtigungenOutFilePath # $KURSSollStellenfunktionenBerechtigungenOutFilePath # $KURSSollStellenfunktionenProfileOutFilePath ####################################################### todo ? }