IAMClient.psm1
function Add-ETHGroupMember { <# .SYNOPSIS Adds members to a IAM group .DESCRIPTION Adds one or more members to a IAM group .EXAMPLE Add-ETHGroupMember biol-micro-isg "aurels" Adds one member "aurels" to the group "biol-micro-isg" .EXAMPLE "somegroup","someothergroup","somethirdgroup" | Add-ETHGroupMember "aurels","jgrand" Adds multiple members to multiple groups .OUTPUTS PSCustomObject An object containing all accepted / rejected / failed members .FUNCTIONALITY Adding members to a group #> [CmdletBinding(SupportsShouldProcess = 1)] param ( [Parameter(Position = 0, Mandatory = 1, ValueFromPipeline = 1)] [string]$Identity, [string[]]$Members ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null # Validate input arguments if (-not ($ExistingGroup = Get-ETHGroup -Identity $Identity)) { throw "Group $Identity was not found" } if ($Members.Count -le 0) { throw "No members specified" } $ToAddMembers = @($Members | Where-Object { $ExistingGroup.members -notcontains $_ }) if ($ToAddMembers.Count -eq 0) { Write-Debug "No new members added to group $Identity" return } } PROCESS { if ($PSCmdlet.ShouldProcess($Identity)) { try { $result = Invoke-IAMMethod -Url "/groupmgr/group/$Identity/members/add" -Method Put -Body $ToAddMembers -Credentials $script:IAMCreds } catch { throw "Could not update group $Identity, Error: $_" return } } } END { Write-Debug "Added $($ToAddMembers.Count) Members to Group $Identity" return $result } } function Add-ETHMaillistMember { param ( # List Name [Parameter(Position = 0, Mandatory = $true)] [string]$Identity, # Member to add or remove [Parameter(Position = 1, Mandatory = $true, ValueFromPipeline = $true)] [string[]]$Members ) BEGIN { Test-IsIAMClientInitialized | Out-Null $Url = "/mailinglists/$Identity/members/add" } PROCESS { return (Invoke-IAMMethod -Url $Url -Body $Members -Method Put -Credentials $script:IAMCreds) } } function Add-ETHUserITService { param ( # ETH user name [Parameter(Position = 0, Mandatory = 1)] [string] $Identity, # IT Service Name [Parameter(Position = 1, Mandatory = 1)] [string] $ITServiceName, # Body [Parameter(Position = 2)] [psobject] $Body ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null if ($Body -eq $null) { $Body = @{ } } } PROCESS { return (Invoke-IAMMethod -Url "/usermgr/user/$Identity/service/$ITServiceName" -Method POST -Credentials $script:IAMCreds -Body $Body) } } function Add-ETHUserMailAlias { <# .SYNOPSIS Adds a new e-mail alias (proxyAddress) to a user's mailbox .DESCRIPTION Adds a new e-mail alias (proxyAddress) to a user's mailbox, this works by changing the main e-mail to the new alias and then back to the existing. !! WARNING: The Cmdlet currently does not reset the main e-mail address again, because IAM does not work (bug as of 14.02.2020) !! .PARAMETER Identity The user to add the alias to .PARAMETER Alias The alias(es) to give to the user .EXAMPLE Add-ETHUserMailAlias -Identity aurels -Alias "aurels.new@ethz.ch","my_cool_email@micro.biol.ethz.ch" #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = 1)] [string]$Identity, [Parameter(Position = 1, Mandatory = 1, ValueFromPipeline = 1)] [string[]]$Alias ) BEGIN { # Check if client is initialized $null = Test-IsIAMClientInitialized $User = Get-ETHUser -Identity $Identity $SavedEmail = $User.mail } PROCESS { foreach ($_alias in $Alias) { if ($User.proxyAdresses -contains "smtp:$_alias"){ # this alias already exists, nothing do to continue } $User.mail = $_alias $User = Set-ETHUser -Identity $Identity -User $User } } END { $User.mail = $SavedEmail Set-ETHUser -Identity $Identity -User $User } } function Clear-ETHMaillistMember { param ( # List Name [Parameter(Position = 0, Mandatory = $true)] [string]$Identity ) BEGIN { Test-IsIAMClientInitialized | Out-Null $Url = "/mailinglists/$Identity/members" } PROCESS { return (Invoke-IAMMethod -Url $Url -Method Delete -Credentials $script:IAMCreds) } } Function Compare-ObjectProperties { Param( [PSObject]$ReferenceObject, [PSObject]$DifferenceObject ) $objprops = $ReferenceObject | Get-Member -MemberType Property, NoteProperty | Select-Object -expand Name $objprops += $DifferenceObject | Get-Member -MemberType Property, NoteProperty | Select-Object -expand Name $objprops = $objprops | Sort-Object -Unique $diffs = @() foreach ($objprop in $objprops) { $diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop if ($diff) { $diffprops = @{ PropertyName = $objprop RefValue = ($diff | Where-Object { $_.SideIndicator -eq '<=' } | Foreach-Object $($objprop)) DiffValue = ($diff | Where-Object { $_.SideIndicator -eq '=>' } | Foreach-Object $($objprop)) } $diffs += New-Object PSObject -Property $diffprops } } if ($diffs) { return ($diffs | Select-Object PropertyName, RefValue, DiffValue) } } Function Get-ObjectDiffs { param ( [PSObject]$ReferenceObject, [PSObject]$DifferenceObject ) $ChangedProps = @{ } Compare-ObjectProperties $ReferenceObject $DifferenceObject | ForEach-Object { $ChangedProps.Add($_.PropertyName, $_.DiffValue); } return $ChangedProps } function Compare-GroupMembers { param ( [string[]]$ExistingMembers, [string[]]$NewMembers ) BEGIN { $ToAddMembers = @() $ToRemoveMembers = @() $ToKeepMembers = @() } PROCESS { if ($ExistingMembers.Count -eq 0) { # No existing members -> add all $ToAddMembers = $NewMembers } elseif ($NewMembers.Count -eq 0) { # No new members -> remove all existing $ToRemoveMembers = $ExistingMembers } else { # everything fine, we can run compare-object $ComparisionResult = Compare-Object -ReferenceObject $ExistingMembers -DifferenceObject $Members -IncludeEqual $ToAddMembers = ($ComparisionResult | Where-Object SideIndicator -eq "=>").InputObject $ToRemoveMembers = ($ComparisionResult | Where-Object SideIndicator -eq "<=").InputObject $ToKeepMembers = ($ComparisionResult | Where-Object SideIndicator -eq "==").InputObject } return [PSCustomObject]@{ ToAdd = $ToAddMembers; ToRemove = $ToRemoveMembers; ToKeep = $ToKeepMembers; } } } function Find-ETHGroup { <# .SYNOPSIS Finds a Group in IAM .DESCRIPTION Finds a Group in IAM with the specified filter .PARAMETER Name The name of the group to find (accepts wildcards *) .PARAMETER AdminGroup The AdminGroup to filter on .EXAMPLE Find-ETHGroup -Name "biol-micro-isg*" In this example, the all groups are found that start with "biol-micro-isg*" .EXAMPLE Find-ETHGroup -Name "id-s4d*" -AdminGroup "D-BIOL" In this example, all groups of Admin Group "D-BIOL" are found that start with "id-s4d" #> param ( [CmdletBinding()] [Parameter(Position = 0)] [string]$Name, [Parameter(Position = 1)] [string]$AdminGroup ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null if (-not $Name -and -not $AdminGroup) { throw "Please specify at least one filter criterium!" } } PROCESS { $url = "/groupmgr/groups?" if ($Name) { $url += "name=$Name" } if ($AdminGroup -and $Name) { $url += "&" } if ($AdminGroup) { $url += "agroup=$AdminGroup" } Invoke-IAMMethod -Url $url -Method Get -Credentials $script:IAMCreds -ErrorAction Stop | ForEach-Object { $_.pstypenames.Insert(0, "ETHZ.ID.IAMClient.IAMGroupSearchResult"); $_ } | Sort-Object AdminGroup, type, Name } END { } } function Get-ETHGroup { <# .SYNOPSIS Gets details of a group from IAM .DESCRIPTION Gets properties (and members) from a group in IAM .EXAMPLE Get-ETHGroup biol-micro-isg Gets the details of a group in iam. .OUTPUTS PSCustomObject An object with all properties of a group .FUNCTIONALITY Loading a group #> param ( [CmdletBinding()] [Parameter(Position = 0, Mandatory = 1)] [string]$Identity ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null $url = "/groupmgr/group/$Identity" } PROCESS { return (Invoke-IAMMethod -Url $url -Method Get -Credentials $script:IAMCreds) } END { } } function Get-ETHGroupMember { <# .SYNOPSIS Gets members of a group in IAM .DESCRIPTION Gets members of a group in IAM .EXAMPLE Get-ETHGroupMember biol-micro-isg Gets all members of the group biol-micro-isg .OUTPUTS String[] A list of user/group names that are members of the group .FUNCTIONALITY Getting members of a group #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = 1, ValueFromPipelineByPropertyName = 1)] [string]$Identity ) BEGIN { $Group = Get-ETHGroup -Identity $Identity } PROCESS { return ($Group | Select-Object -expand members) } END { } } function Get-ETHMaillist { param ( # List Name [Parameter(Position = 0, Mandatory = $true)] [string]$Identity ) BEGIN { Test-IsIAMClientInitialized | Out-Null $Url = "/mailinglists/$Identity" } PROCESS { return (Invoke-IAMMethod -Url $Url -Method Get -Credentials $script:IAMCreds) } } function Get-ETHMaillistMember { [CmdletBinding()] param ( # List Name [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [string]$Identity ) BEGIN { } PROCESS { $membersCn = (Get-ETHMaillist -Identity $Identity).members foreach ($member in $membersCn) { $Name = Convert-CnToName $member # determine if the member is a user or a mailinglist if ($member -like "*OU=EthLists,*") { $objectClass = "group" } else { $objectClass = "user" } # create simple return object [pscustomobject]@{ name = $Name; objectClass = $objectClass; distinguishedName = $member; } } } } function Get-ETHPerson { param ( [Parameter(Position = 0, Mandatory = 1)] [string]$Identity ) BEGIN { $url = "/usermgr/person/$Identity" # is client initialized? Test-IsIAMClientInitialized | Out-Null } PROCESS { return (Invoke-IAMMethod -Url $url -Method Get -Credentials $script:IAMCreds) } END { } } function Get-ETHPersonServices { param ( # ETH user name [Parameter(Position = 0, Mandatory = 1)] [String]$Identity ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null } PROCESS { return (Invoke-IAMMethod -Url "/usermgr/user/$Identity/services" -Method GET -Credentials $script:IAMCreds) } } function Get-ETHUser { <# .SYNOPSIS Gets the parameters of a IT-Service for a user (Similar to Get-ADUser) .DESCRIPTION Gets all parameters for a given service from IAM for the given user Default is the "Mailbox" service .PARAMETER Identity The username to find .PARAMETER Service The service name to get the parameters for .EXAMPLE Get-ETHUser aurels .EXAMPLE Get-ETHUser aurels -Service LDAP #> param ( [Parameter(Position = 0, Mandatory = 1, ValueFromPipeline = $true)] [string]$Identity, [Parameter(Position = 1, Mandatory = 0)] [string]$Service = "Mailbox" ) BEGIN { $url = "/usermgr/user/$Identity/service/$Service" # is client initialized? Test-IsIAMClientInitialized | Out-Null } PROCESS { $result = Invoke-IAMMethod -Url $url -Method Get -Credentials $script:IAMCreds } END { return $result } } function Get-ETHUserGroupMembership { <# .SYNOPSIS Gets all memberships of the given user .DESCRIPTION This will load all group memberships with type "Custom","Admin" and "Netsup" for the given user(s) .PARAMETER Identity The username to find .EXAMPLE Get-ETHUserGroupMembership aurels .EXAMPLE "aurels","jgrand" | Get-ETHUserGroupMembership #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = "Name")] [string]$Identity ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null } PROCESS { $url = "/groupmgr/user/$Identity" if (-not (Get-ETHUser $Identity)) { throw "User $Identity could not be found" } $groups = (Invoke-IAMMethod -Url $url -Method Get -Credentials $script:IAMCreds).Groups $groups | Add-Member -MemberType NoteProperty -Value $Identity -Name "User" # set type for all objects $groups | ForEach-Object { $_.pstypenames.Insert(0, "ETHZ.ID.IAMClient.IAMGroupMembership") } return ($groups | Sort-Object -property Type, Name) } END { } } Function Write-RequestToConsole { param ( [string]$Method, [hashtable]$Headers, [string]$JsonBody ) Write-Verbose "-- || -- || -- || -- || --" Write-Verbose "$($Method.toUpper()) $Uri" foreach ($h in $Headers.Keys) { # Do not print basic auth string to console, instead override with some value if ($h -ne "Authorization") { Write-Verbose "${h}: $($Headers[$h])" } else { Write-Verbose "${h}: Basic BasicAuthString99999=" } } Write-Verbose "Body: $JsonBody" } function Write-ResponseToConsole { param ( $Response ) Write-Verbose "------ RESPONSE:" if ($null -ne $Response) { Write-Verbose (ConvertTo-Json $Response) } } function Convert-CnToName { [CmdletBinding()] param( # Canonical Name [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [string]$Cn ) PROCESS { return ($Cn -split ",")[0] -replace "CN=" } } function ParseHttpException { [CmdLetBinding()] param( [Parameter(Position = 0, Mandatory = $true)] [System.Management.Automation.ErrorRecord]$InputObject ) # if response is a string, is contains a json object if ($null -ne $InputObject.Exception.Response) { # read answer from stream # > use .ToString() and string comparision instead of -is operator # > as the -is operator requires a vaild type, which is not the case in PowerShell 6 (type does not exist) if ($InputObject.Exception.Response.GetType().FullName -eq "System.Net.HttpWebResponse") { # PowerShell 5 $responseStream = $InputObject.Exception.Response.GetResponseStream() # create streamreader to read from stream $streamReader = New-Object System.IO.StreamReader -ArgumentList $responseStream # read $responseStream.Seek(0, [System.IO.SeekOrigin]::Begin) $errResponse = $streamReader.ReadToEnd() # clean up reader and stream $streamReader.Dispose() $responseStream.Dispose() } else { # PowerShell 6+ $errResponse = $InputObject.ErrorDetails.Message } # try to parse Response message if (-not [string]::IsNullOrEmpty($errResponse)) { try { $errObject = ConvertFrom-Json $errResponse $errMessage = ($errObject.level + " -> " + $errObject.message) } catch { $errMessage = $errResponse } $newException = New-Object System.Exception -ArgumentList $errMessage, $InputObject.Exception # response did not contain valid JSON, return original error message ( see below ) return $newException } # we did not get any additional info from the error message, just throw the original message # throw original error return $InputObject } } function IsPsWindows { <# .SYNOPSIS Used as fallback for the $isWindows variable on PSv5 and before #> if (Get-Variable -Name "isWindows" -ErrorAction SilentlyContinue) { return $isWindows } return ($PSVersionTable.PSVersion.Major -le 5) } function Test-IsIAMClientInitialized { if ($null -eq $script:IAMCreds) { # remove all "normal" output from this cmdlet -> do not display hints Initialize-IAMClient 6>$null return $true } return $true } function SaveCredToCredMan { <# .SYNOPSIS Stores a pscredential in the PwManager #> [CmdLetBinding()] param( [Parameter(Mandatory = $true)] [pscredential]$Credential, [Parameter(Mandatory = $true)] [string]$PwIdentifier ) $PwVault = InitCredMan -ErrorAction Stop $PwCred = New-Object Windows.Security.Credentials.PasswordCredential -ArgumentList ($PwIdentifier, $Credential.UserName, $Credential.GetNetworkCredential().Password) $PwVault.Add($PwCred) } function RetreiveCredFromCredMan { <# .SYNOPSIS Retrieves a password stored in the windows credential manager #> [CmdLetBinding()] param( [Parameter(Mandatory = $true)] [string]$PwIdentifier ) $PwVault = InitCredMan -ErrorAction Stop $PwCreds = $PwVault.FindAllByResource($PwIdentifier) if ($PwCreds.Count -ne 1) { throw "Found $($PwCreds.Count) matching credentials for identifier '$($PwIdentifier)'. Please clean up credman!" } # read password $PwCreds[0].RetrievePassword() return [pscredential]::new($PwCreds[0].UserName, (ConvertTo-SecureString $PwCreds[0].Password -AsPlainText -Force)) } function IsPwStoredInCredMan { <# .SYNOPSIS Checks if a password is stored in the windows credential manager #> [CmdLetBinding()] param( [Parameter(Mandatory = $true)] [string]$PwIdentifier ) $PwVault = InitCredMan -ErrorAction Stop try { return @($PwVault.FindAllByResource($PwIdentifier)).Count -ge 1 } catch { return $false } } function InitCredMan { <# .SYNOPSIS Initializes the Credential Manager and throws an error when is does not exist #> [CmdLetBinding()] param () try { return [Windows.Security.Credentials.PasswordVault, Windows.Security.Credentials, ContentType = WindowsRuntime]::new() } catch { throw [System.NotSupportedException]::new("Credential manager is only supported on Windows", $_.Exception) } } function Initialize-IAMClient { <# .SYNOPSIS Initializes the IAMClient to work with the API .DESCRIPTION Performs a login to the IAM Api and saves the credentials for the current session (or optionally permanent) .PARAMETER Credential The login to use when logging in to the API. If not given, PowerShell will ask manually for username / password. .PARAMETER SaveCredential When given, stores the password in the Windows Credential Manager for later re-use. **This works only on Windows!** .PARAMETER EnableDebugOutput Use this parameter to enable verbose logging if an error occures and you are not sure if the API answers correctly. The verbose output can used in e-mail communication (very recommended!) .PARAMETER ApiHost You can specify a custom API endpoint if you want f.ex to connect to the QSS environment. .EXAMPLE Initialize-IAMClient myuser4ea Signs in to the IAM api with the user "myuser4ea", you will be prompted for the password. .EXAMPLE Initialize-IAMClient myuser4ea -SaveCred See Example 1, but stores the credential in the Windows Credential Manager You will **never** need to perform the initialize command again! .FUNCTIONALITY Use this CmdLet to connect the PS Module with the API. #> [CmdletBinding()] param( # Credentials to validate [Parameter(Position = 0, Mandatory = $false)] [Alias("Credentials")] [pscredential]$Credential, [Parameter(Mandatory = $false)] [switch]$SaveCredential, # included for backwards compatibility [Parameter(DontShow = $true)] [switch]$Force, # for debug purposes [switch]$EnableDebugOutput, [Parameter(Mandatory = $false)] [ValidateScript({[Uri]::IsWellFormedUriString($_, [UriKind]::Absolute)})] [string]$ApiHost = "https://iam.passwort.ethz.ch/iam-ws-legacy/" ) if ($null -eq $Credential) { if ((IsPsWindows) -and (IsPwStoredInCredMan -PwIdentifier $ApiHost)){ $Credential = RetreiveCredFromCredMan -PwIdentifier $ApiHost } else { $Credential = Get-Credential -Message "Enter your credentials for IAM" } } # store the API host for the module to use $script:ApiHost = $ApiHost # message about force not being used. # TODO Remove in Version 2.0 if ($Force -eq $true) { Write-Warning "The -Force switch is included for backwards compatibility only, it has no functionality" } # Enable Debug mode for script if ($EnableDebugOutput) { $script:DebugMode = $true $VerbosePreference = "Continue" } else { $VerbosePreference = "SilentlyContinue" } # test credentials and fail if they were entered wrong if (-not (Test-ETHCredentials $Credential)) { $script:IAMCreds = $null throw "Could not validate your credentials" } if ($SaveCredential) { # save credential to credman SaveCredToCredMan -Credential $Credential -PwIdentifier $ApiHost } elseif ((IsPsWindows) -and -not (IsPwStoredInCredMan -PwIdentifier $ApiHost)) { # hints about new functionality Write-Host -f Cyan "HINT: Use the -SaveCredential switch to save your password to the Windows Credential Manager!" Write-Host -f Cyan "HINT: The password will then be stored secure and permanent!" Write-Host -f Cyan "Example: PS> Initialize-IAMClient -SaveCredential" } # save credentials in module $script:IAMCreds = $Credential Set-StrictMode -Version latest } Function Invoke-IAMMethod { [CmdletBinding(SupportsShouldProcess = 1)] param ( [Parameter(Position = 0, Mandatory = 1)] [string]$Url, [Parameter(Mandatory = 1)] [Microsoft.PowerShell.Commands.WebRequestMethod]$Method, [Parameter(Position = 1)] [psobject]$Body = "", [Parameter(Position = 2)] [pscredential]$Credentials ) BEGIN { $Headers = @{ } If ($Credentials -ne $null) { $AuthHeader = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($Credentials.UserName):$($Credentials.GetNetworkCredential().Password)")); # only set auth header when needed $Headers.Add("Authorization", $AuthHeader); } if ($Body -ne "") { if ($Body -is [Array]) { $JsonBody = ConvertTo-Json @($Body) -Compress } else { $JsonBody = ConvertTo-Json $Body -Compress } } else { $JsonBody = "" } # Accept header $Headers.Add("Accept", "application/json"); if (-not [string]::IsNullOrWhiteSpace($JsonBody)) { $Headers.Add("Content-type", "application/json; charset=utf-8"); } # Form complete URL and parse it $Uri = $script:ApiHost + $Url if (-not [uri]::IsWellFormedUriString($Uri, "Absolute")) { throw "Could not parse URI $Uri" return } } PROCESS { if ($PSCmdlet.ShouldProcess($Url)) { if ($script:DebugMode) { Write-RequestToConsole -Method $Method.ToString() -Headers $Headers -JsonBody $JsonBody } try { # only provide the body when needed, as it gives an error when used with GET if ($Method -eq [Microsoft.PowerShell.Commands.WebRequestMethod]::Get) { $Response = Invoke-RestMethod -Uri $Uri -Method $Method -Headers $Headers } else { $Response = Invoke-RestMethod -Uri $Uri -Method $Method -Headers $Headers -Body $JsonBody } # Only write to console if debug is enabled if ($script:DebugMode) { Write-ResponseToConsole -Response $Response } } catch { # parse the error message and throw the output throw (ParseHttpException -InputObject $_) } } } END { return $Response } } function New-ETHGroup { <# .SYNOPSIS Creates a new group in IAM .DESCRIPTION Creates a new group in IAM with the given properties .PARAMETER Name The name of the group .PARAMETER Description The description to give the group .PARAMETER Targets The targets to export the group to. Valid are "AD" and "LDAPS" (both can be specified) .PARAMETER AdminGroup The Admin Group to assign this group to .EXAMPLE New-ETHGroup -Name "biol-micro-isg-testgroup_api" -Description "20191203/asc: TestGroup" -Targets AD -AdminGroup "D-BIOL" This example creates one group, with the name "biol-micro-isg-testgroup_api" with a description and Admin Group "D-BIOL" exported to AD only. .EXAMPLE "biol-micro-testgroup1","biol-micro-testgroup2" | New-ETHGroup -Description "20191203/asc: TestGroup" -Targets AD -AdminGroup "D-BIOL" -Members "aurels","jgrand" This example creates two groups, both with the same members, "aurels" and "jgrand" #> param ( [CmdletBinding()] [Parameter(Position = 0, Mandatory = 1, ValueFromPipeline = $true)] [string]$Name, [Parameter(Position = 1, Mandatory = $false)] [string]$Description, [Parameter(Position = 2, Mandatory = $false)] [ValidateSet("AD", "LDAPS")] [string[]]$Targets, [Parameter(Position = 3, Mandatory = $true)] [string]$AdminGroup, [Parameter(Position = 4, Mandatory = $false)] [string[]]$Members ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null $url = "/groupmgr/group" } PROCESS { $Body = @{ "name" = $Name "description" = $Description "admingroup" = $AdminGroup "members" = $Members "targets" = $Targets } Invoke-IAMMethod -Url $url -Method Post -Body $Body -Credentials $script:IAMCreds } END { } } function New-ETHPersona { param( # Username of Persona parent [Parameter(Position = 0, Mandatory = 1)] [string] $ParentIdentity, # New Username [Parameter(Position = 1, Mandatory = 1)] [string] $NewUserName, [Parameter(Position = 2)] [string] $UserComment = "", # Initial Password [Parameter(Position = 3, Mandatory = 1)] [string] $InitPwd ) BEGIN { $NewUser = [PSCustomObject]@{ username = $NewUserName; memo = $UserComment; init_passwd = $NewPassword; } } PROCESS { Invoke-IAMMethod -Url "/usermgr/person/$ParentIdentity" -Method POST -Credentials $script:IAMCreds -Body $NewUser } } function Remove-ETHGroup { <# .SYNOPSIS Deletes a group in IAM .DESCRIPTION Deletes a group with the specified name in IAM .PARAMETER Name The name of the group .EXAMPLE Remove-ETHGroup -Name "biol-micro-isg-testgroup_api" In this example, the group "biol-micro-isg-testgroup_api" is deleted .EXAMPLE Get-ETHGroup "biol-micro-isg-testgroup_api" | Remove-ETHGroup In this example, the group "biol-micro-isg-testgroup_api" is deleted using the pipeline. #> param ( [CmdletBinding()] [Parameter(Position = 0, Mandatory = 1, ValueFromPipelineByPropertyName = "name")] [string]$Name ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null } PROCESS { $url = "/groupmgr/group/$Name" Invoke-IAMMethod -Url $url -Method Delete -Credentials $script:IAMCreds return $Name } END { } } function Remove-ETHGroupMember { <# .SYNOPSIS Removes members from a group in IAM .DESCRIPTION Removes members from a group in IAM .EXAMPLE Remove-ETHGroupMember -Identity "biol-micro-isg" -Members "aurels","jgrand" Removes two members from the group "biol-micro-isg" .EXAMPLE "somegroup","someothergroup","somethirdgroup" | Remove-ETHGroupMember -Members "aurels","jgrand" Removes multiple members from multiple groups .FUNCTIONALITY Removing users from groups #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [string]$Identity, [Parameter(Position = 1, Mandatory = $true)] [string[]]$Members ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null # check if any members were specified if ($Members.Count -le 0) { throw "No members specified" } } PROCESS { # Validate group exists if (-not ($ExistingGroup = Get-ETHGroup -Identity $Identity)) { throw "Group $Identity was not found" } # get list of users to remove (skip non-members) $ToRemoveMembers = @($Members | Where-Object { $ExistingGroup.members -contains $_ }) # Check if there are any members in the group that need to be removed if ($ToRemoveMembers.Count -eq 0) { Write-Debug "Did not need to remove any members from $Identity" return $ExistingGroup } if ($PSCmdlet.ShouldProcess($Identity)) { try { return (Invoke-IAMMethod -Url "/groupmgr/group/$Identity/members/del" -Method Put -Body $ToRemoveMembers -Credentials $script:IAMCreds) } catch { throw "Could not update group $Identity" } } } END { } } function Remove-ETHMaillist { [CmdletBinding(SupportsShouldProcess = $true)] param ( # List Name [Parameter(Position = 0, ParameterSetName = "ByName")] [string]$Identity, [Parameter(ValueFromPipeline = $true, ParameterSetName = "ByPipeline")] [psobject]$MailObject ) BEGIN { Test-IsIAMClientInitialized | Out-Null if ($MailObject -ne $null) { $Identity = $MailObject.listName } $Url = "/mailinglists/$Identity" } PROCESS { if ($PSCmdlet.ShouldProcess("Deleting Mailliglist $Identity")) { return (Invoke-IAMMethod -Url $Url -Method Delete -Credentials $script:IAMCreds) } } } function Remove-ETHMaillistMember { param ( # List Name [Parameter(Position = 0, Mandatory = $true)] [string]$Identity, # Member to add or remove [Parameter(Position = 1, Mandatory = $true, ValueFromPipeline = $true)] [string[]]$Members ) BEGIN { Test-IsIAMClientInitialized | Out-Null $Url = "/mailinglists/$Identity/members/del" } PROCESS { return (Invoke-IAMMethod -Url $Url -Body $Members -Method Put -Credentials $script:IAMCreds) } } function Reset-ETHUserPassword { param ( [Parameter(Position = 0, Mandatory = 1)] [string]$Identity, # Parameter help description [Parameter(Position = 1, Mandatory = 1)] [securestring]$NewPassword, [Parameter(Position = 2, Mandatory = 1)] [string]$ServiceName ) BEGIN { $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($NewPassword) $PlainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) } PROCESS { try { $result = Invoke-IAMMethod -Url "/usermgr/user/$Identity/service/$ServiceName/password" -Body @{password = $PlainText } -Credentials $script:IAMCreds -Method Put return $result; } catch { Write-Error "Could not reset the password of the user $Identity, Error: $_" return; } } } function Set-ETHGroup { <# .SYNOPSIS Edits an existing group in IAM .DESCRIPTION Sets specific properties of an existing group in IAM .PARAMETER Identity The name of the group .PARAMETER NewName Rename the group to this new name .PARAMETER Description Set the description of this group .PARAMETER Targets !!Not implemented in IAM!! The targets to export the group to. Valid are "AD" and "LDAPS" (both can be specified) .PARAMETER AdminGroup !!Not implemented in IAM!! The Admin Group to assign this group to .EXAMPLE Set-ETHGroup -Identity "biol-micro-api_test" -Description "NewDescription" Change the description of the group "biol-micro-api_test". #> param ( [CmdletBinding()] [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = "name")] [string]$Identity, [Parameter(Position = 1, Mandatory = $false)] [string]$NewName, [Parameter(Position = 2, Mandatory = $false)] [string]$Description, [Parameter(Position = 3, Mandatory = $false)] [ValidateSet("AD", "LDAPS")] [string[]]$Targets, [Parameter(Position = 4, Mandatory = $false)] [string]$AdminGroup ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null } PROCESS { $url = "/groupmgr/group/$Identity" $Body = @{ } if ($NewName) { $Body["name"] = $NewName } if ($Description) { $Body["description"] = $Description } if ($Targets) { $Body["targets"] = $Targets } if ($AdminGroup) { $Body["admingroup"] = $AdminGroup } if ($body.Count -gt 0) { Invoke-IAMMethod -Url $url -Method Put -Body $Body -Credentials $script:IAMCreds -ea Continue } $Identity = if ($NewName) { $NewName } else { $Identity } Get-ETHGroup $Identity } END { } } function Set-ETHGroupMember { <# .SYNOPSIS Sets the members of an ETH group to the specified member list .DESCRIPTION Removes / Adds members to the given group until the memberlist is equal to the one submitted You can specify either usernames or *custom* groups .PARAMETER Identity The group to edit .PARAMETER Members The list of members to set the group memberlist to .EXAMPLE PS> Set-ETHGroupMember -Identity biol-micro-isg -Members @("aurels","ausc") .EXAMPLE PS> Set-ETHGroupMember -Identity biol-micro-isg -Members @("biol-micro-isg-sadm","aurels") Added: {"aurels", "ausc"} Removed: {} Kept: {} .OUTPUTS pscustomobject. Returns a custom object with 3 properties Added, Removed and Kept to show what the cmdlet did #> [CmdletBinding(SupportsShouldProcess = $true, HelpUri = "https://gitlab.ethz.ch/aurels/iam-powershell/tree/master/docs/Set-ETHGroupMember.md")] [OutputType([PSCustomObject])] param( # Group Name [Parameter(Position = 0, Mandatory = $true)] [string]$Identity, # Members to sync to [Parameter(Position = 1, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = "Name")] [AllowEmptyCollection()] [string[]] $Members ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null try { $ExistingMembers = @(Get-ETHGroupMember $Identity) } catch { throw "Could not find group $Identity" } } PROCESS { # get members to be removed & added $MemberCompare = Compare-GroupMembers -ExistingMembers $ExistingMembers -NewMembers $Members $ToBeAdded = $MemberCompare.ToAdd $ToBeRemoved = $MemberCompare.ToRemove try { if ($ToBeAdded.Count -gt 0) { if ($PSCmdlet.ShouldProcess($Identity, "Add-ETHGroupMember")) { # Discard output of Add-ETHGroupMember $null = Add-ETHGroupMember -Identity $Identity -Members $ToBeAdded } } if ($ToBeRemoved.Count -gt 0) { if ($PSCmdlet.ShouldProcess($Identity, "Remove-ETHGroupMember")) { $null = Remove-ETHGroupMember -Identity $Identity -Members $ToBeRemoved } } } catch { throw "Failed to update group membership of group $Identity, try again to restore group integrity!`r`nError: $_" } return @{ Added = $ToBeAdded; Removed = $ToBeRemoved; Kept = $MemberCompare.ToKeep; } } END { } } function Set-ETHMaillistMembers { [CmdletBinding()] param ( # Maillist name [Parameter(Position = 0, Mandatory = $true)] [string] $Identity, # Members [Parameter(Position = 1, Mandatory = $true)] [string[]]$Members ) BEGIN { Test-IsIAMClientInitialized | Out-Null try { $ExistingMembers = @((Get-ETHMaillistMember -Identity $Identity).name) } catch { throw "Could not find Mailinglist $Identity" } } PROCESS { $MemberCompare = Compare-GroupMembers -ExistingMembers $ExistingMembers -NewMembers $Members $ToAddMembers = $MemberCompare.ToAdd $ToRemoveMembers = $MemberCompare.ToRemove # Add members try { if ($ToAddMembers.Count -gt 0) { $null = Add-ETHMaillistMember -Identity $Identity -Members $ToAddMembers Write-Debug "Successfully added $($ToAddMembers.Count) new members to Mailinglist $Identity" } } catch { Write-Error "Failed to add $($ToAddMembers.Count) members to mailinglist. $([System.Environment]::NewLine)Error: $_" return } # Remove members try { if ($ToRemoveMembers.Count -gt 0) { $null = Remove-ETHMaillistMember -Identity $Identity -Members $ToRemoveMembers Write-Debug "Successfully removed $($ToRemoveMembers.Count) members from Mailinglist $Identity" } } catch { Write-Error "Failed to remove $($ToRemoveMembers.Count) members from mailinglist. $([System.Environment]::NewLine)Error: $_" return } return @{ Added = $ToAddMembers; Removed = $ToRemoveMembers; Kept = $MemberCompare.ToKeep; } } } function Set-ETHUser { <# .SYNOPSIS Sets the parameters of an IT-Service for a user (Similar to Set-ADUser) .DESCRIPTION Sets all given paramters to the given values .PARAMETER Identity The username to update .PARAMETER User The modified user object to save, use the output of Get-ETHUser. .EXAMPLE PS C:\> $user = Get-ETHUser aurels PS C:\> $user.homeDrive = "P:" PS C:\> $user.homeDirectory = "\\server\share\%username%" PS C:\> $user.profilePath = "" PS C:\> Set-ETHUser -Identity aurels -User $user #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = 1)] [string]$Identity, [Parameter(Position = 1, Mandatory = 1, ValueFromPipeline = 1)] [psobject]$User, [Parameter(Position = 2, Mandatory = 0)] [string]$Service = "Mailbox" ) # Check if client is initialized $null = Test-IsIAMClientInitialized $sourceUser = Get-ETHUser -Identity $Identity $changedProperties = Get-ObjectDiffs $sourceUser $User if ($changedProperties.Count -eq 0){ # nothing to do return $sourceUser } Invoke-IAMMethod -Url "/usermgr/user/$Identity/service/$Service" -Method Put -Body $changedProperties -Credentials $script:IAMCreds } function Set-ETHUserITService { param ( # ETH user name [Parameter(Position = 0, Mandatory = 1)] [string] $Identity, # IT Service Name [Parameter(Position = 1, Mandatory = 1)] [string] $ITServiceName, # Body [Parameter(Position = 2, Mandatory = 1)] [psobject] $Body ) BEGIN { # Check if client is initialized Test-IsIAMClientInitialized | Out-Null } PROCESS { return (Invoke-IAMMethod -Url "/usermgr/user/$Identity/service/$ITServiceName" -Method Put -Credentials $script:IAMCreds -Body $Body) } } function Sync-ETHGroupMember { <# .SYNOPSIS Synchronizes users from multiple groups and mailing lists to a group and a mailinglist .DESCRIPTION Copies all **users** from the source groups/lists to the given destination group/list .PARAMETER SourceGroups The list of groups to read members from .PARAMETER SourceLists The list of mailinglists to read members from .PARAMETER DestGroup The destination group that will be set to all members from the given groups / lists .PARAMETER DestList The destination mailinglist that will be set to all members from the given groups / lists .EXAMPLE PS> Sync-ETHGroupMember -SourceGroups "biol-micro-isg" -DestList "MICRO_IT_STAFF" Copies all members from the group "biol-micro-isg" to the Maillinglist "MICRO_IT_STAFF" .EXAMPLE PS> Sync-ETHGroupMember -SourceLists "MICRO_IT_STAFF","MICRO_AD_STAFF" -DestList "MICRO_STAFF" Copies all members from the source lists to the destination list .EXAMPLE PS> Sync-ETHGroupMember -SourceLists "MICRO_IT_STAFF","MICRO_AD_STAFF" -SourceGroups "biol-micro-institute" -DestGroup "biol-micro-institute" Adds all members from the given lists to the destination group without removing users .OUTPUTS System.Collections.Hashtable A hashtable with a report on each group/list that was modified and what was done (Add / Remove / Keep Members) #> [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = "ToGroup")] [OutputType([System.Collections.HashTable])] param( # Group Name [Parameter(Position = 0)] [Alias("ReferenceGroups")] [string[]]$SourceGroups, # Mailinglist to sync from [Parameter(Position = 1)] [string[]]$SourceLists, # Group to sync members to [Parameter(Position = 2, ParameterSetName = "ToGroup", Mandatory = 1)] [Parameter(ParameterSetName = "ToBoth", Mandatory = 1)] [Alias("SyncGroup")] [string]$DestGroup, # List to sync members to [Parameter(Position = 3, ParameterSetName = "ToList", Mandatory = 1)] [Parameter(ParameterSetName = "ToBoth", Mandatory = 1)] [string]$DestList, # Falls back to AD if group cannot be loaded via IAM [Parameter()] [switch]$AllowADFallback ) BEGIN { # Validate input arguments if ($SourceGroups.Count -eq 0 -and $SourceLists.Count -eq 0) { throw "At least one source group or list has to be specified!" } if ([string]::IsNullOrWhiteSpace($DestGroup) -and [string]::IsNullOrWhiteSpace($DestList)) { throw "At least one destination group has to be specified!" } if ($AllowADFallback -and (Get-Module).Name -notcontains "ActiveDirectory") { try { Import-Module ActiveDirectory } catch { throw "To use the ActiveDirectory fallback, install RSAT tools!" } } # Check if client is initialized Test-IsIAMClientInitialized | Out-Null # Validate destination group exists if ($DestGroup) { try { $null = Get-ETHGroup -Identity $DestGroup # discard output } catch { throw "Could not find group $DestGroup" } } if ($DestList) { try { $null = Get-ETHMaillist -Identity $DestList # discard output } catch { throw "Could not find list $DestList" } } $ListsToProcess = @() $GroupsToProcess = @() if ($SourceLists.Count -gt 0) { $ListsToProcess = @($SourceLists | ForEach-Object { [PSCustomObject]@{Name = $_; Type = "List" } }) } if ($SourceGroups.Count -gt 0) { $GroupsToProcess = @($SourceGroups | ForEach-Object { [PSCustomObject]@{Name = $_; Type = "Group" } }) } } PROCESS { # Store all members from the different sourcegroups in a hashset, # so that duplicates are automatically eliminated $AllMembersList = New-Object 'System.Collections.Generic.HashSet[string]' foreach ($Source in @($ListsToProcess + $GroupsToProcess)) { # retrieve type for output messages $SourceType = $Source.Type try { switch ($SourceType) { "Group" { # Get Group members $Group = Get-ETHGroup $Source.Name $AllMembersList.UnionWith([string[]]@($Group.members)) } "List" { # Get Maillist members $ListMembers = Get-ETHMaillistMember $Source.Name | Where-Object objectClass -eq "user" $AllMembersList.UnionWith([string[]]@($ListMembers.name)) # add all members to the list } Default { # Invalid throw "GroupType '$SourceType' invalid. Valid are 'List','Group'!" } } } catch { # Group / List was not found in IAM # Perform ad fallback if needed if (-not $AllowADFallback) { throw "Could not find $SourceType '$($Source.Name)' in IAM" } try { # get all users from AD group as fallback $Members = Get-ADGroupMember -Identity $Source.Name | Where-Object objectClass -eq "user" $AllMembersList.UnionWith([string[]]($Members.name)) } catch { throw "Could not find $SourceType '$($Source.Name)' in AD" } } } # Store changes in a hashtable for every group modified $Changes = @{ } if ($DestGroup -ne "" -and $PSCmdlet.ShouldProcess($DestGroup, "Set-ETHGroupMember")) { $Changes.Add($DestGroup, (Set-ETHGroupMember -Identity $DestGroup -Members $AllMembersList)) } if ($DestList -ne "" -and $PSCmdlet.ShouldProcess("$DestList", "Set-ETHMaillistMembers")) { $Changes.Add($DestList, (Set-ETHMaillistMembers -Identity $DestList -Members $AllMembersList)) } return $Changes } END { } } function Test-ETHCredentials { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = 1)] [ValidateNotNull()] [pscredential]$Credentials ) $script:IAMCreds = $Credentials try { Get-ETHUser -Identity $Credentials.UserName -ErrorAction Stop return $true } catch { # write error to error stream as non-terminating error Write-Error $_ return $false } finally { $script:IAMCreds = $null } } $script:IAMCreds = $null $script:ApiHost = "" # will set during initialization $script:DebugMode = $false if ($PSVersionTable.PSVersion.Major -le 5){ # set TLS1.2 as default when running in PSv5 # if TLS1.3 is out, this should be specified # https://docs.microsoft.com/en-us/security/engineering/solving-tls1-problem#update-windows-powershell-scripts-or-related-registry-settings [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 } Export-ModuleMember -Function 'Invoke-IAMMethod','Add-ETHGroupMember','Find-ETHGroup','Get-ETHGroup','Get-ETHGroupMember','New-ETHGroup','Remove-ETHGroup','Remove-ETHGroupMember','Set-ETHGroup','Set-ETHGroupMember','Sync-ETHGroupMember','Add-ETHMaillistMember','Clear-ETHMaillistMember','Get-ETHMaillist','Get-ETHMaillistMember','Remove-ETHMaillist','Remove-ETHMaillistMember','Set-ETHMaillistMembers','Get-ETHPerson','Get-ETHPersonServices','New-ETHPersona','Add-ETHUserITService','Add-ETHUserMailAlias','Get-ETHUser','Get-ETHUserGroupMembership','Reset-ETHUserPassword','Set-ETHUser','Set-ETHUserITService','Initialize-IAMClient','Test-ETHCredentials' |