InvokePersonio.psm1
|
# Invoke-Personio # Lokale Sitzungen lesen das Personio Client-Secret und Access-Tokens aus dem Windows Credential Manager. # In Azure Automation Runbooks wird das Client-Secret aus Get-AutomationPSCredential gelesen und pro Job ein frisches Access-Token nur im Arbeitsspeicher gehalten. # Version 1.8.0 19.03.2026 by Klaus Kupferschmid (tempero.it GmbH & hhpberlin GmbH) #Requires -Modules @{ ModuleName = 'BetterCredentials'; ModuleVersion = '4.5' } $script:PersonioSettings = [ordered]@{ ServiceUserName = "HHPBERLIN_USERMANAGER" BaseUri = "https://api.personio.de/v1/company/employees" AuthUri = "https://api.personio.de/v1/auth?" MailDomain = "hhpberlin.de" } function Update-PersonioDerivedSettings { $script:servicePERUserName = $script:PersonioSettings.ServiceUserName $script:Personio_uri = $script:PersonioSettings.BaseUri.TrimEnd('/') $script:Personio_auth_uri = $script:PersonioSettings.AuthUri $script:mailDomain = $script:PersonioSettings.MailDomain $script:Personio_access_token_1 = $script:servicePERUserName+"_PersonioAccessToken_1" $script:Personio_access_token_2 = $script:servicePERUserName+"_PersonioAccessToken_2" } function Get-PersonioConfiguration { [CmdletBinding()] param () return [pscustomobject]@{ ServiceUserName = $script:PersonioSettings.ServiceUserName BaseUri = $script:PersonioSettings.BaseUri AuthUri = $script:PersonioSettings.AuthUri MailDomain = $script:PersonioSettings.MailDomain AccessToken1 = $script:Personio_access_token_1 AccessToken2 = $script:Personio_access_token_2 } } function Set-PersonioConfiguration { [CmdletBinding()] param ( [Parameter(Mandatory=$false)][string]$ServiceUserName, [Parameter(Mandatory=$false)][string]$BaseUri, [Parameter(Mandatory=$false)][string]$AuthUri, [Parameter(Mandatory=$false)][string]$MailDomain ) if ($PSBoundParameters.ContainsKey('ServiceUserName')) { $script:PersonioSettings.ServiceUserName = $ServiceUserName } if ($PSBoundParameters.ContainsKey('BaseUri')) { $script:PersonioSettings.BaseUri = $BaseUri.TrimEnd('/') } if ($PSBoundParameters.ContainsKey('AuthUri')) { $script:PersonioSettings.AuthUri = $AuthUri } if ($PSBoundParameters.ContainsKey('MailDomain')) { $script:PersonioSettings.MailDomain = $MailDomain.TrimStart('@') } Update-PersonioDerivedSettings return Get-PersonioConfiguration } Update-PersonioDerivedSettings function Invoke-Personio { <# .Synopsis Connect to Personio Database by REST-API. .Description Connect Personio Database by REST-API. Returns Employee(s) as PSCustomObject(s) .Parameter Endpoint Endpoint is the Appendix of Rest-URL (https://api.personio.de/v1/company/employees). .Parameter Method Method defines what will be done with the selected Endpont. Allowed Strings are GET .Parameter limit Pagination attribute to limit the number of employees returned per page. .Parameter offset Pagination attribute to identify the first item in the collection to return. .Example # Retrieve all Employees from Personio Invoke-Personio "?" .Example # Get all Personio-Records by Email-Address. Invoke-Personio "?email=c.abel%40hhpberlin.de" Status active standard status Position Position standard position Employment type internal standard employment_type Hire date 2010-03-01T00:00:00+01:00 date hire_date Contract ends date contract_end_date Termination date date termination_date Termination type standard termination_type Probation period end date probation_period_end Created at 2020-03-13T14:08:29+01:00 date created_at Office @{type=Office; attributes=} standard office Profile Picture https://api.personio.de/v1/company/employees/1807395/profile-picture standard profile_picture Personalnummer 123 standard hhpberlin Kürzel ABC standard Jobtitel Jobtitel standard Ersteintrittsdatum date first_company_entry_date Eintrittsdatum (aktueller Beschäftigungszeitraum) 2010-03-01T00:00:00+01:00 date latest_employment_start_date Austrittsdatum (aktueller Beschäftigungszeitraum) date latest_employment_end_date #> [CmdletBinding()] Param ( [parameter(Position=0,Mandatory=$true)][String] $endpoint, [parameter(Position=1,Mandatory=$false)][String] $method = 'GET', [parameter(Position=2,Mandatory=$false)] $body, [parameter(Position=3,Mandatory=$false)][int32] $limit = 50, # it is mendatory since 7.11.2024, otherwise you only get the fisrt 50 [parameter(Position=4,Mandatory=$false)][int32] $offset = 0, # mendatory if limit is set [parameter(Mandatory=$false)][switch] $RawOutput ) $token_credentials = Get-Creds if (!$token_credentials -or $token_credentials.Count -eq 0) { Write-Host "Fehler: Keine Credentials gefunden" -ForegroundColor Red return } $headers=@{} $headers.Add("Accept", "application/json") $headers.Add("X-Personio-Partner-ID", $Personio_client_id) $headers.Add("X-Personio-App-ID", $servicePERUserName) $headers.Add("content-type", "application/json") # Kombiniere die beiden Token-Teile (zusammen können sie länger als 256 Zeichen sein) $token_str1 = "" $token_str2 = "" if ($token_credentials[0] -and $token_credentials[0].Password) { $token_str1 = Convert-SecureStringToPlainText -SecureString $token_credentials[0].Password } if ($token_credentials.Count -gt 1 -and $token_credentials[1] -and $token_credentials[1].Password) { $token_str2 = Convert-SecureStringToPlainText -SecureString $token_credentials[1].Password } $bearerToken = $token_str1 + $token_str2 $headers.Add("Authorization", "Bearer $bearerToken") if ($body) { if ($body.GetType().Name -eq "Hashtable"){ $body = $body | ConvertTo-Json $body = EscapeNonAscii $body } } Switch -Regex ($endpoint){ '\?' {$uri = $Personio_uri+"?limit=$limit&offset=$offset&"} '\?email' {$uri = $Personio_uri+$endpoint} "^\d+$" {$uri = $Personio_uri+"/"+$endpoint} "/\d+$" {$uri = $Personio_uri+$endpoint} # match if String starts with / followed by Numbers = ID Default {$uri = $Personio_uri+$endpoint} } if($method -eq "PATCH"){ $uri = $uri+'/' } <#elseif ($method -ne '/'){ $uri = $uri+'?' }#> #$uri = $uri+$endpoint $Error.clear() try { If ($method -eq "PATCH"){ $response = Invoke-WebRequest -Uri $uri -Method $method -Headers $headers -Body $body -UseBasicParsing -ErrorAction Stop }else{ $response = Invoke-WebRequest -Uri $uri -Method $method -Headers $headers -UseBasicParsing -ErrorAction Stop } } catch { switch -RegEx ($PSItem.Exception.Message) { "401" { Write-Host "personio_token ist falsch WebRequest-Error: 401" if (-not $env_runbook) { Remove-StoredCredentialSafe -Target $Personio_access_token_1 Remove-StoredCredentialSafe -Target $Personio_access_token_2 } If ($Error_401){ $script:Error_401 = $null throw 'Fehler "401 Nicht autorisiert" tritt zum zweiten mal auf.' }else{ $Error.clear() $script:Error_401 = $true $null = Get-Creds -renew $true $responseObject = Invoke-Personio -endpoint $endpoint -method $method -body $body -limit $limit -offset $offset -RawOutput:$RawOutput $response = $Null } } "404" { Write-Host 'Fehler "404" - Benutzer existiert nicht in der Personio Datenbank' -ForegroundColor "Yellow" $script:Error_401 = $null Break } Default { Write-Host $_ -ForegroundColor Yellow $script:Error_401 = $null } } } if ($response.Content) { # Splitt token and save for next request $token = ($response.Headers.authorization).Split(" ")[1] if ($token.length -ge 201){ $token1 = $token.substring(0,200) $token2 = $token.substring(200) }else{ $token1 = $token $token2 = "" } if (-not $env_runbook) { # API Token might be longer than 200 characters, so it is stored in two local credential entries. Remove-StoredCredentialSafe -Target $Personio_access_token_1 Remove-StoredCredentialSafe -Target $Personio_access_token_2 Set-Credential -Target $Personio_access_token_1 -Credential (New-Object System.Management.Automation.PSCredential('BearerToken', (ConvertTo-SecureString -String $token1 -AsPlainText -Force))) -Type Generic -Description "Personio AccessToken 1" -Persistence Enterprise >$Null if ($token2) { Set-Credential -Target $Personio_access_token_2 -Credential (New-Object System.Management.Automation.PSCredential('BearerToken', (ConvertTo-SecureString -String $token2 -AsPlainText -Force))) -Type Generic -Description "Personio AccessToken 2" -Persistence Enterprise >$null } } $token = $Null $token1 = $Null $token2 = $Null # read Data.attributes $responseObject = ((ConvertFrom-Json $response.Content).Data.attributes) } if($method -eq "GET"){ $return = @() # WICHTIG: Array initialisieren! if($responseObject){ if ($responseObject.psobject.properties.value[0].gettype().Name -eq "Int32") { if($responseObject.psobject.properties.value[4]){ # create $employees-Array cut first and last 3 Object (Respond on "Get-Emplyee" indicator for this is first Array-Object is an Integer) $currentPage = @( $responseObject.psobject.properties.value[4..($responseObject.psobject.properties.value.count -4)] | Where-Object { $_ -and $_ -isnot [bool] } ) $return += $currentPage Write-Host "Seite $([math]::Floor($offset / $limit) + 1): $($currentPage.count) Employees geladen, gesamt: $($return.count)" -ForegroundColor "Cyan" # Rekursive Aufrufe - wichtig: Rückgabewert erfassen! $recursiveResults = Invoke-Personio -method 'GET' -limit $limit -offset ($offset+$limit) -endpoint $endpoint -RawOutput if ($recursiveResults) { $return += $recursiveResults } }else{ # Probably was read the Attributes only $return = (ConvertFrom-Json $response.Content).data $response = $Null } }else{ # Single employee response: keep the raw attribute object so it can be converted consistently. $return += $responseObject } } # Ausgabe nur wenn wir am Ende sind (offset=0 = first call, nur dann wird die Nachricht ausgegeben) if ($offset -eq 0 -and $return.count -gt 0) { if ($return.count -eq 1) { Write-Host "Es wurde folgender Employee ausgelesen:" -ForegroundColor "Green" } else { Write-Host "$($return.count) Employees wurden insgesamt ausgelesen:" -ForegroundColor "Green" } } if ($RawOutput -or $endpoint -eq '/attributes') { return $return } return ConvertTo-UserObject $return } } function Get-Employee { <# .Synopsis Retrieve employe Object(s) from Personio .Description OAuth-Connect Personio Database by REST-API. Returns Employee(s) as PSCustomObject(s) .Parameter Identity Accept either email or Personio-ID as a String. .Parameter email Accept email as a String. .Parameter id Accept Personio-ID as a int. .Example # Retrieve all Employees from Personio Get-Employee .Example # Get a Unique Employee Record from Email-Adress. Get-Employee c.abel@hhpberlin.de id : 1234567 first_name : Vorname last_name : Nachname email : v.nachname@hhpberlin.de status : active position : Position supervisor : @{type=Employee; attributes=} employment_type : internal hire_date : 01.03.2010 00:00:00 contract_end_date : termination_date : termination_type : probation_period_end : created_at : 13.03.2020 14:08:29 office : @{type=Office; attributes=} profile_picture : https://api.personio.de/v1/company/employees/1234567/profile-picture Personalnummer : 123 hhpberlin_Kuerzel : AAA actual_training_end_date : Jobtitel : Jobtitel expected_training_end_date : training_start_date : work_permit_expiry_date : first_company_entry_date : latest_employment_start_date : 01.03.2010 00:00:00 latest_employment_end_date : .Example # Get a Unique Employee Record from Personio-ID. Get-Employee 1234567 #> [CmdletBinding()] param ( [parameter(Position=1,Mandatory=$false,ValueFromPipeline)]$identity, [parameter(Position=2,Mandatory=$false)][int] $id, [parameter(Position=3,Mandatory=$false)][String] $email ) $endpoint = Get-Endpoint if ($identity){ $endpoint = Get-Endpoint $identity }elseif ($id){ $endpoint = Get-Endpoint $id }elseif ($email){ $endpoint = Get-Endpoint $email } return Invoke-Personio $endpoint } function Set-Employee { <# .Synopsis Set Attribute Value in Personio Databae .Description OAuth-Connect Personio Database by REST-API. Returns Employee. Set new Attribute Value .Parameter Identity Accept either email or Personio-ID as a String. .Parameter email Accept email as a String. .Parameter id Accept Personio-ID as a int. .Example # Set new Value 'Ja' to the attribute 'mobile_Phone_number! Set-Employee -Id 1234567 -attribute mobile_Phone_number -Value 'Ja' .Example # Set new Value 'Ja' to the attribute 'mobile_Phone_number! Set-Employee t.eins Mobiltelefon Nein #> [CmdletBinding()] param ( [parameter(Mandatory=$false,ValueFromPipeline)]$identity, [parameter(Position=2,Mandatory=$true)][String] $attribute, [parameter(Position=3,Mandatory=$true)][String] $value, [parameter(Position=4,Mandatory=$false)][int] $id, [parameter(Position=1,Mandatory=$false)][String] $email ) if ($identity){ $endpoint = Get-Endpoint $identity }elseif ($id){ $endpoint = Get-Endpoint $id }elseif ($email){ $endpoint = Get-Endpoint $email } $employee = Invoke-Personio ($endpoint) $user = $employee $body = New-Body $attribute $value if ($employee){ $success = Invoke-Personio -endpoint (Get-Endpoint $user.id) -method "PATCH" -body $body }else{ throw "Benutzer in Personio nicht gefunden." } return $success } function Sync-MobileOwner{ [CmdletBinding()] param ( [Parameter(Mandatory=$false)]$PhoneContracts, [Parameter(Mandatory=$false)][scriptblock]$PhoneContractsProvider ) Write-Host "Lese alle aktiven Employees aus Personio aus" -noNewline -ForegroundColor "Green" Write-Host "." -noNewline try { $allEmployees = Get-Employee | Where-Object status -eq "active" Write-Host "OK" -ForegroundColor "Green" } catch { throw "Fehler beim Auslesen der Personio-Datenbank." } Write-Host "Lese alle aktiven Telefonverträge aus Inventory360 aus" -noNewline -ForegroundColor "Green" Write-Host "." -noNewline if ($PSBoundParameters.ContainsKey('PhoneContracts')) { $phoneContracts = @($PhoneContracts) | Where-Object Status -eq "Active" Write-Host "OK" -ForegroundColor "Green" } else { if (-not $PhoneContractsProvider) { $readPhoneContractsCommand = Get-Command -Name Read-PhoneContracts -ErrorAction SilentlyContinue if (-not $readPhoneContractsCommand) { throw "Read-PhoneContracts ist nicht verfuegbar. Uebergib -PhoneContracts oder -PhoneContractsProvider." } $PhoneContractsProvider = { Read-PhoneContracts } } try { $phoneContracts = & $PhoneContractsProvider | Where-Object Status -eq "Active" Write-Host "OK" -ForegroundColor "Green" } catch { throw "Fehler beim Auslesen der Inventory360-Datenbank." } } foreach ($employee in $allEmployees) { $contract = $phoneContracts | Where-Object username -eq ($employee.email.split('@')[0]) $employee_Mobiltelefon = ($employee |Where-Object {$_.Mobiltelefon -eq "Ja" -or $_.Mobiltelefon -eq "Nein"}).Mobiltelefon if(($contract -and ($employee_Mobiltelefon -eq "Ja")) -or (!$contract -and ($employee_Mobiltelefon -eq "Nein"))){ Write-Host "$($employee.email): Mobiltelefon Eintrag in Personio stimmt mit AD überein" -ForegroundColor "Green" }else{ if (!$contract) {$employee_Mobiltelefon = "Nein"} else{$employee_Mobiltelefon = "Ja"} Write-Host "$($employee.email): Mobiltelefon Eintrag in Personio stimmt nicht mit AD überein. Setzte '$($employee_Mobiltelefon)' in Personio" -ForegroundColor "Yellow" -noNewline try { Write-Host "." -noNewline Set-Employee -id $employee.id -attribute "Mobiltelefon" -value $employee_Mobiltelefon -ErrorAction Stop Write-Host "OK" -ForegroundColor "Green" } catch { throw "Fehler beim Schreiben in die Personio-Datenbank." } } } } function Show-Attributes{ <# .Synopsis Shows a list of available attributes with the authentication used. .Description OAuth-Connect Personio Database by REST-API. Returns a List of Attributes as CustomObject .Example # Shows a list of available attributes with the authentication used. Show-Attributes key label type universal_id --- ----- ---- ------------ first_name First name standard first_name last_name Last name standard last_name email Email standard email status Status standard status position Position standard position supervisor Supervisor standard supervisor employment_type Employment type standard employment_type hire_date Hire date date hire_date contract_end_date Contract ends date contract_end_date termination_date Termination date date termination_date termination_type Termination type standard termination_type termination_reason Termination reason standard termination_reason probation_period_end Probation period end date probation_period_end created_at Created at date created_at last_modified_at Last modified date last_modified_at subcompany Subcompany standard subcompany office Office standard office department Department standard department absence_entitlement Absence entitlement standard absence_entitlement last_working_day Last day of work date last_working_day profile_picture Profile Picture standard profile_picture team Team standard team dynamic_919111 Personalnummer standard dynamic_1003506 Akademischer Grad standard name_academic_title dynamic_1271341 hhpberlin Kürzel standard dynamic_3600510 Ausbildungsende date actual_training_end_date dynamic_1395613 Jobtitel standard dynamic_1625230 Private E-Mailadresse standard dynamic_919131 Beschäftigungsart list dynamic_3600522 Ausbildungsbeginn date training_start_date dynamic_3600527 Vorsatzwort list name_prefix dynamic_1285115 Gültig bis date dynamic_3600531 Ersteintrittsdatum date first_company_entry_date dynamic_9673413 Mobiltelefon list #> Invoke-Personio -Endpoint "/attributes" } # private functions function New-Body { param ( [parameter(Position=1,Mandatory=$true,ValueFromPipeline)][String] $attribute, [parameter(Position=2,Mandatory=$true)][String] $value ) $label = ($employee.PSobject.Properties.Value | Where-Object label -eq $attribute).label if (!$label){$label = (Show-Attributes | Where-Object universal_id -eq $attribute).label} if (!$label){$label = (Show-Attributes | Where-Object key -eq $attribute).label} if ($label){ $key = (Show-Attributes | Where-Object label -eq $label).key if ($key -match "dynamic_"){ $body = @{employee = @{"custom_attributes" = @{$key = $value }}} }else{ $body = @{employee = @{ $key = $value }} } }else{ throw "Das Attribut $attribute bzw. die dazugehörige GUID existiert nicht in Personio." } return $body } function ConvertTo-UserObject ([parameter(Position=1,Mandatory=$false,ValueFromPipeline)]$employees){ $users = @() foreach ($employee in $employees) { $attributeValues = @() if ($employee -and $employee.PSObject.Properties.Name -contains 'employee') { $attributeValues = @($employee.employee) } elseif ($employee) { $attributeValues = @($employee.PSObject.Properties | ForEach-Object Value) } if ($attributeValues.Count -eq 0) { continue } $user = New-Object System.Object foreach ($property in $attributeValues) { if (-not ($property.PSObject.Properties.Name -contains 'value' -and $property.PSObject.Properties.Name -contains 'type')) { continue } switch ($property.type) { integer { if ($null -ne $property.value){$property.value = [int]$property.value}} date { if ($null -ne $property.value){$property.value = Get-Date $property.value}} } if ($property.universal_id){ $noteProperty = $property.universal_id }else{ $noteProperty = $property.label.replace(' ','_') $noteProperty = $noteProperty.replace('ü','ue') $noteProperty = $noteProperty.replace('ä','ae') $noteProperty = $noteProperty.replace('ö','oe') $noteProperty = $noteProperty.replace('ß','ss') } if ($null -eq $property.value){ $user | Add-Member -type NoteProperty -name $noteProperty -Value $null }else { $user | Add-Member -type NoteProperty -name $noteProperty -Value $property.value } } $users += $user } return $users } function EscapeNonAscii([string] $s) { # wird nur in Powershell 5.1 benutzt $sb = New-Object System.Text.StringBuilder; for ([int] $i = 0; $i -lt $s.Length; $i++) { $c = $s[$i]; if ($c -gt 127) { $sb = $sb.Append("\u").Append(([int] $c).ToString("X").PadLeft(4, "0")); } else { $sb = $sb.Append($c); } } return $sb.ToString() } function Get-CustomIdFromAttribute { param ( [parameter(Position=1,Mandatory=$false,ValueFromPipeline)]$attribute ) $attributes = Invoke-Personio -Endpoint "/attributes" $key = ($attributes | Where-Object label -eq $attribute).key If ($key){ return $key }else{ throw "Das Attribut $attribute existiert nicht oder ist nicht fuer die API freigegeben." } } function Get-Endpoint{ param ( [parameter(Position=1,Mandatory=$false,ValueFromPipeline)]$par ) if($par){ if($par.gettype().Name -eq "Object"){ $par = $par.email } switch -Regex ($par) { "\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z" {$endpoint = "?email="+$par.replace('@','%40')} #UPN "^[a-zA-Z0-9]+\.[a-zA-Z0-9]+$" {$endpoint = "?email="+$par+'%40'+$mailDomain} # SAMaccountname "^\d+$" {$endpoint = '/'+$par} # ID "attributes" {$endpoint = "/attributes"} Default {throw "Identity entspricht weder ID noch email."} } }else{ $endpoint = '?' } return $endpoint } function Convert-SecureStringToPlainText { param ( [Parameter(Mandatory=$false)] [Security.SecureString] $SecureString ) if ($null -eq $SecureString) { return "" } $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { if ($bstr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } } } function Initialize-AutomationEnvironment { if ($script:automationEnvironmentInitialized) { return } $script:automationEnvironmentInitialized = $true $script:env_runbook = $false $script:env_runbook_HybridWorker = $false try { if ($PSPrivateMetadata.JobId) { $script:env_runbook = $true } } catch { $script:env_runbook = $false } if ($script:env_runbook -and $env:COMPUTERNAME -and $env:AUTOMATION_WORKER_NAME) { if ($env:COMPUTERNAME -eq $env:AUTOMATION_WORKER_NAME) { $script:env_runbook_HybridWorker = $true } } } function Get-StoredCredentialSafe { param ( [Parameter(Mandatory=$true)] [string] $Target ) try { return Find-Credential -Filter $Target | Select-Object -First 1 } catch { return $null } } function Remove-StoredCredentialSafe { param ( [Parameter(Mandatory=$true)] [string] $Target ) try { Remove-Credential -Target $Target -Type Generic -ErrorAction Stop } catch { } } function New-PersonioTokenCredentialObjects { param ( [Parameter(Mandatory=$true)] [string] $Token ) $token1 = $Token $token2 = "" if ($Token.Length -ge 201) { $token1 = $Token.Substring(0,200) $token2 = $Token.Substring(200) } $credentials = @( New-Object System.Management.Automation.PSCredential('BearerToken', (ConvertTo-SecureString -String $token1 -AsPlainText -Force)) ) if ($token2) { $credentials += New-Object System.Management.Automation.PSCredential('BearerToken', (ConvertTo-SecureString -String $token2 -AsPlainText -Force)) } return $credentials } function Get-PersonioClientCredential { Initialize-AutomationEnvironment if ($env_runbook) { $automationCredentialCommand = Get-Command -Name Get-AutomationPSCredential -ErrorAction SilentlyContinue if (-not $automationCredentialCommand) { throw "Get-AutomationPSCredential ist in dieser Runbook-Umgebung nicht verfuegbar." } try { $credential = Get-AutomationPSCredential -Name $servicePERUserName -ErrorAction Stop } catch { throw "AutomationPSCredential mit dem Namen $servicePERUserName konnte nicht gelesen werden." } if (-not $credential) { throw "AutomationPSCredential mit dem Namen $servicePERUserName wurde nicht gefunden." } $script:Personio_client_id = $credential.UserName return $credential } $storedCred = Get-StoredCredentialSafe -Target $servicePERUserName if ($storedCred) { $script:Personio_client_id = $storedCred.UserName return $storedCred } Write-Host "Personio Client-ID & Secret wurde noch nicht festgelegt!" -ForegroundColor "Yellow" $cred = Microsoft.PowerShell.Security\Get-Credential -Message 'Geben Sie "Personio ClientID" als Benutzername und "Secret" als Passwort ein' Set-Credential -Target $servicePERUserName -Credential $cred -Type Generic -Persistence Enterprise -Description "Personio Client ID & Secret" >$null $script:Personio_client_id = $cred.UserName return $cred } function Request-PersonioAccessToken { param ( [Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential] $Credential ) # In Runbooks the access token is requested for the current job and only returned in memory. $script:Personio_client_id = $Credential.UserName $headers = @{ "Accept" = "application/json" "X-Personio-App-ID" = $servicePERUserName } $body = @{ "client_id" = $Credential.UserName "client_secret" = Convert-SecureStringToPlainText -SecureString $Credential.Password } try { $response = Invoke-WebRequest -Uri $Personio_auth_uri -Method POST -Headers $headers -Body $body -UseBasicParsing -ErrorAction Stop } catch { if ($PSItem.Exception.Message -match "403") { if (-not $env_runbook) { Remove-StoredCredentialSafe -Target $servicePERUserName } if ($Error_403) { $script:Error_403 = $null throw 'Fehler 403 tritt zum zweiten Mal auf.' } $Error.Clear() $script:Error_403 = $true return Request-PersonioAccessToken -Credential (Get-PersonioClientCredential) } $script:Error_403 = $null if ($PSItem.Exception.Message -match "The response content cannot be parsed") { throw "Die Antwort konnte nicht geparst werden. Pruefen Sie die PowerShell-HTTP-Antwortverarbeitung in der aktuellen Umgebung." } throw } $script:Error_403 = $null if (-not $response.Content) { throw "Personio hat kein Access Token zurueckgegeben." } return (ConvertFrom-Json $response.Content).Data.token } function Write-TokenToCredentialManager{ # This helper is intended for local sessions where client secret and access token are cached locally. if (-not $script:Personio_client_id) { $null = Get-PersonioClientCredential } $storedSecret = Get-StoredCredentialSafe -Target $servicePERUserName if ($storedSecret -and -not $script:Personio_client_id) { $script:Personio_client_id = $storedSecret.UserName } if ([string]::IsNullOrWhiteSpace($script:Personio_client_id)) { throw 'Personio Client-ID konnte nicht ermittelt werden.' } if (!$storedSecret) { # Prompt once locally and persist the client secret for subsequent local sessions. Write-Host "Personio Client Secret wird benötigt" -ForegroundColor "Yellow" $cred = Microsoft.PowerShell.Security\Get-Credential -UserName $Personio_client_id -Message 'Geben Sie das "Personio Client Secret" ein' if (-not $cred) { throw 'Personio Client-ID & Secret wurden nicht eingegeben.' } Set-Credential -Target $servicePERUserName -Credential $cred -Type Generic -Persistence Enterprise -Description "Personio Client ID & Secret" >$null $personio_client_secret = $cred.Password } else { $personio_client_secret = $storedSecret.Password } $headers = @{ "Accept" = "application/json" "X-Personio-App-ID" = $servicePERUserName } $body = @{ "client_id" = $Personio_client_id "client_secret" = Convert-SecureStringToPlainText -SecureString $personio_client_secret } try { $response = Invoke-WebRequest -Uri $Personio_auth_uri -Method POST -Headers $headers -body $body -UseBasicParsing -ErrorAction Stop } catch { If ($PSItem.Exception.Message -match "403"){ Write-Host "personio_client_secret ist falsch WebRequest-Error: 403" Remove-StoredCredentialSafe -Target $servicePERUserName If ($Error_403){ $script:Error_403 = $null throw 'Fehler 403 tritt zum zweiten Mal auf.' }else{ $Error.clear() $script:Error_403 = $true Write-TokenToCredentialManager } }else{ $script:Error_403 = $null } If ($PSItem.Exception.Message -match "The response content cannot be parsed"){ throw "Die Antwort konnte nicht geparst werden. Pruefen Sie die PowerShell-HTTP-Antwortverarbeitung in der aktuellen Umgebung." } } $personio_client_secret = $null IF($response.Content){ $token = (ConvertFrom-Json $response.Content).Data.token if ($token.length -ge 201){ $token1 = $token.substring(0,200) $token2 = $token.substring(200) }else{ $token1 = $token $token2 = "" } # Locally the token is split across two credential entries because it may exceed one entry size. Set-Credential -Target $Personio_access_token_1 -Credential (New-Object System.Management.Automation.PSCredential('BearerToken', (ConvertTo-SecureString -String $token1 -AsPlainText -Force))) -Type Generic -Description "Personio AccessToken 1" -Persistence Enterprise >$Null # Only store the second token part when it exists. if ($token2) { Set-Credential -Target $Personio_access_token_2 -Credential (New-Object System.Management.Automation.PSCredential('BearerToken', (ConvertTo-SecureString -String $token2 -AsPlainText -Force))) -Type Generic -Description "Personio AccessToken 2" -Persistence Enterprise >$null } else { Remove-StoredCredentialSafe -Target $Personio_access_token_2 } $response = $Null $token1 = $Null $token2 = $Null } $credentials = @() $cred1 = Get-StoredCredentialSafe -Target $Personio_access_token_1 $cred2 = Get-StoredCredentialSafe -Target $Personio_access_token_2 if ($cred1) { $credentials += $cred1 } if ($cred2) { $credentials += $cred2 } return $credentials } function Get-Creds { Param ( [parameter(Position=0,Mandatory=$false,ValueFromPipeline)] $connectionTarget="PER", [parameter(Position=1,Mandatory=$false)][boolean] $renew=$false ) Initialize-AutomationEnvironment switch ($connectionTarget) { PER { } Default { throw "Internal Error: Get-Creds wurde mit einem unbekannten Dienst-Kuerzel aufgerufen." } } if ($env_runbook) { # Runbooks always obtain a fresh token from the stored client credential and keep it only in memory. $clientCredential = Get-PersonioClientCredential $token = Request-PersonioAccessToken -Credential $clientCredential return @(New-PersonioTokenCredentialObjects -Token $token) } if ([string]::IsNullOrWhiteSpace($script:Personio_client_id)) { $storedClientCredential = Get-StoredCredentialSafe -Target $servicePERUserName if ($storedClientCredential) { $script:Personio_client_id = $storedClientCredential.UserName } } $credential = @() try { $cred1 = Get-StoredCredentialSafe -Target $Personio_access_token_1 $cred2 = Get-StoredCredentialSafe -Target $Personio_access_token_2 if ($cred1) { $credential += $cred1 } if ($cred2) { $credential += $cred2 } } catch { $Error } if ($renew -or $credential.count -lt 1 -or $null -in $credential) { $null = Get-PersonioClientCredential $credential = Write-TokenToCredentialManager } return $credential } # Import credentials for the current runtime environment # Public functions Export-ModuleMember Invoke-Personio Export-ModuleMember Get-Employee Export-ModuleMember Set-Employee Export-ModuleMember Sync-MobileOwner Export-ModuleMember Show-Attributes Export-ModuleMember Get-PersonioConfiguration Export-ModuleMember Set-PersonioConfiguration |