Azure DevOps Get Current User ObjectId - powershell

Is there a way to get the ObjectId of the Service Principal that is currently executing an Azure PowerShell task in Azure DevOps at all?
I am creating a resource, and then want to apply permissions for the 'current' user.. but can't work out how to get the current user ObjectId / ApplicationId
Is this possible?

Ok - based of the above, I've made a little function - it may work for a lot of cases:
function Get-CurrentUserObjectID {
$ctx = Get-AzContext
#This is different for users that are internal vs external
#We can use Mail for users and guests
$User = Get-AzADUser -Mail $ctx.Account.id
if (-not $user) { #Try UPN
$User = Get-AzADUser -UserPrincipalName $ctx.Account.Id
}
if (-not $User) { #User was not found by mail or UPN, try MailNick
$mail = ($ctx.Account.id -replace "#","_" ) + "#EXT#"
$User = Get-AzADUser | Where-Object { $_MailNick -EQ $Mail}
}
Return $User.id
}

There seems to be two ways of doing this depending on if it's a user, or a service principal:-
Get-AzADUser
Get-AzADServicePrincipal
These i believe are in the Az.Resources module. So, to give you the ObjectId (for permissions), you could take a two step approach like this:
$x = (Get-AzContext).Account.Id
$x
> df6fc4f6-cb05-4301-91e3-11d93d7fd43d # ApplicationId
$y = Get-AzADServicePrincipal -ApplicationId $x
$y.Id
> c3588e6a-b48b-4111-8241-3d6bd726ca40 # ObjectId
I can't get anything to work reliably with standard users though.. if your user is created in the AzureAD directly, and not external (i.e. gmail.com, outlook.com, etc) this should work:
$x = (Get-AzContext).Account.Id
$x
> sample#your-domain.onmicrosoft.com # UserPrincipalName
$y = Get-AzADUser -UserPrincipalName $x
$y.Id
> c5d4339b-48dc-4190-b9fb-f5397053844b # ObjectId
If your user is external, and has the weird your.email.address_outlook.com#EXT##your-domain.onmicrosoft.com as the UserPrincipalName you'll need to solve that with a bit of string manipulation i think 😕.
But! You shouldn't be scripting things with user accounts anyway, so it probably doesn't matter 😆.
Note: I have not tried this in Azure DevOps, you will probs need to upgrade the PowerShell packages, but i think the same commands should exist as Get-AzureRmADUser, and Get-AzureRmADServicePrincipal. Please let me know.

Related

Powershell Script for Okta API to add single group to Multiple Apps

I have a group called Group1 and I have approx 50-60 similar apps that contain the same name like "ABC-423", "ABC-4242" etc.
What i want is to add this single group(Group-1) to this different apps "ABC*" by using Okta API with Powershell (API - https://github.com/gabrielsroka/OktaAPI.psm1)
here is the code I wrote so far
#Purpose of Code : We want to Add Group named GRPS1 to Okta Apps that's name contains ABC
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# [1] Connect to Okta. Do this before making any other calls. - done
Connect-Okta "<redacted>" "https://yourorg.oktapreview.com"
$url = "https://yourorg.oktapreview.com"
$appdata = #()
$groupid = "00gtx2yruqKg9diIJ0h7" #00gtx2yruqKg9diIJ0h7 => GRPS1
$group = 0
$i=0
$j=0
#Function to Get all Active OKTA Application List
function Get-AllActiveApps($url)
{
# [2] Now we want to Store JSON data from $appdata to Powershell Object - done
$appdata = #(Invoke-Method GET "/api/v1/apps?status eq 'Active'")
foreach($i in $appdata[0].label)
{
# [3.1] Use a For loop to get one by one application object checked with name contaning ABC
if ($i -like '*ABC*')
{
# [3.2] Assign Group to all apps that's name contains ABC
$appnumber = $appdata[0][$j].id
Add-OktaAppGroup($appnumber,$groupid,$group)
}
else
{
}
$j++
}
}
Get-AllActiveApps($url)
The problem is at function Add-OktaAppGroup($appnumber,$groupid,$group) because the loop it is passing all application-ids as one single array and the function can only process one group id at a time
Can anyone help how to solve this?
Your Get-AllActiveApps function is pretty much useless. After taking out all the parts that don't have anything to do with the task of getting all active apps, we would be left with
function Get-AllActiveApps
{
Invoke-Method GET '/api/v1/apps?filter=status eq "Active"'
}
and that function already exists in OktaAPI.psm1, in a better form:
function Get-OktaApps($filter, $limit = 20, $expand, $url = "/api/v1/apps?filter=$filter&limit=$limit&expand=$expand&q=$q", $q)
{
# ...
}
So let's use this instead, with the -filter parameter.
The rest of your task (filtering some more, and doing something for each item) is PowerShell 101. You don't need any custom functions or loops for that, a pipeline is enough:
Connect-Okta -baseUrl "https://yourorg.oktapreview.com" -token "<redacted>"
# using a hash for group IDs allows easy reference in the rest of the code
$groups = #{
GRPS1 = "00gtx2yruqKg9diIJ0h7"
}
$activeApps = Get-OktaApps -filter 'status eq "ACTIVE"' -limit 200
$activeApps.objects | Where-Object label -like '*ABC*' | Where-Object {
$appGroups = Get-OktaAppGroups -appid $_.id -limit 200
$appGroups.objects.id -notcontains $groups.GRPS1
} | ForEach-Object {
Add-OktaAppGroup -appid $_.id -groupid $groups.GRPS1
}
Notes
You don't need to set the [Net.ServicePointManager]::SecurityProtocol, Connect-Okta already does that.
App groups are not part of the API response when you get a list of apps, that's why the second Where-Object fetches the app groups for each app.
$appGroups.objects.id will be an array of the IDs of all returned groups. -notcontains $groups.GRPS1 produces $true or $false, depending. This will be the filter criterion for Where-Object, i.e. "all apps where the list of group IDs does not contain that particular ID".
Many Okta API responses are paged. 200 objects per page seems to be the upper limit, as per the API documentation. Pay attention to situations where there are more objects than that. Read the documentation to OktaAPI.psm1 to learn how to handle these cases, because the code above does not.
You should probably add some debugging output. Read about Write-Debug and the $DebugPreference global variable.
Note that the Okta Apps API can let you search by name using the -q parameter, eg
Get-OktaApps -q "ABC-"
It will return up to 200 apps; you can paginate for more.
See https://developer.okta.com/docs/reference/api/apps/#list-applications

Enter-PSSession, try with multiple credentials

I need to create a script that requires multiple credentials for establishing the connection to the computers.
I have tried to achieve this with a "try catch" and "if else", but the script looks pretty ugly and is not scalable if i need to insert more credentials.
There is a better way to achive the same results?
#credentiasl for non domain conputers and domain joined
$credential01 = Get-Credential domain 1\administrador
$credentiall02 = Get-Credential domain 2\administrador
$credentiall03 = Get-Credential workgroup\administrador
$error.clear()
#try to establish a remote sesion to the pc with the credentials01
try { etsn -ComputerName sldb04 -Credential $credential01 }
#if there is an error try to establish a remote sesion to the pc with the credentials02
catch
{
etsn -ComputerName sldb04 -Credential $credential02
#if the second credential is also wrong try the third
If ($? -eq $false )
{
etsn -ComputerName sldb04 -Credential $credential03
}
}
As a best practice, don't attempt to guess the right account. This creates noise in the security logs and might lead into unexpected account lockouts.
Record the proper credentials for each environment and use it. You could store the credential mappings in a hash table like so,
# Add computer names and respective admin accounts into a hash table
$ht = #{"computer01"="domain1\admin";"computer02"="domain2\admin";"computer03"="group\admin" }
# What's the account for computer01?
$ht["computer01"]
domain1\admin
# Print all computers and admin account names
$ht.GetEnumerator() | % { $("Logon to {0} as {1}" -f $_.name, $_.value) }
Logon to computer01 as domain1\admin
Logon to computer03 as group\admin
Logon to computer02 as domain2\admin
This can easily be extended by creating another a hashtable that stores credentials. Like so,
# Create empty hashable
$creds = #{}
# Iterate computer/account hashtable and prompt for admin passwords
$ht.GetEnumerator() | % {
$c= get-credential $_.value
# Add only accounts that don't yet exist on the hashtable
if(-not $creds.Contains($_.value)) {
$creds.Add($_.value, $c) }
}
# What's the account for domain1\admin?
$creds["domain1\admin"]
UserName Password
-------- --------
domain1\admin System.Security.SecureString

Line break issue when configuring "send on behalf of"

I have a script to set send on behalf of permissions in Exchange Management Shell, but when you try and use it it fails because the output of the first part is too long and truncates over 2 lines.
First thing we do is build our array from lists of people and put them into some variables to pass:
function Add-Send ($mailbox, $target) {
#"Granting send on behalf for $mailbox to $target"
Set-Mailbox -Identity $mailbox -GrantSendOnBehalfTo #{ Add = $target }
}
We pass a long list as the $target and the maibox name is $mailbox and if we output the text we get:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson" }
All fine and good but if there are more than N characters in the output then we get a line break:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson", ...
..., "cath.cathdotir" }
When you run this script with the overlength output, then command fails as the output which should be passed to the CLI is passed over more than one line. PowerShell treats each line as a separate command, and they obviously fail with bad syntax.
Our string is output from an array that we build like this:
function Send-Array ($mailbox) {
$target = Get-Content ".\list\dpt1.txt"
$target += Get-Content ".\list\$mailbox.txt"
$target += Get-Content ".\list\dpt2.txt"
$target = $target | Select-Object -Unique
$separator = '", "'
$target= $target -replace '^|$','"' -join ','
Add-Send $mailbox $target
}
This gives us an array with strings that look like:
"alan.alanson", "bob.bobson", "steve.stevenson"
From here I am at a loss any ideas would be much appreciated.
The obvious solution would be to pass the names one at a time, but due to a gotcha with Exchange Server every time you set send on behalf of permissions with PowerShell it wipes the existing permissions, so you only end up with he last person granted permissions being able to send on behalf of.
See this link for help with your underlying issue.
Very basically, you will have to:
get the DistinguishedName of the user you need to add
store the current value of GrantSendOnBehalfTo in a variable
append the new user's distinguished name to the list
replace GrantSendOnBehalfTo with the new list
Afterwards you should not need to pass endless strings to the EMS (I hope so).

Search GC replicas and find AD account

I have an issue which is to do with AD replication. We use a 3rd party app the create accounts in AD and then a powershell script (called by the app) to create the exchange accounts.
In the 3rd party app we can not tell which GC the ad account has been created on and therefore have to wait 20 minutes for replication to happen.
What I am trying to do is find which GC the account has been created on or is replicated to and connect to that server using....
set-adserversettings -preferredserver $ADserver
I currently have the below script and what I can't work out is to get it to stop when it finds the account and assign that GC to the $ADserver variable. The write-host line is only there for testing.
$ForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$GCs = $ForestInfo.FindAllGlobalCatalogs()
Import-module activedirectory
ForEach ($GC in $GCs)
{
Write-Host $GC.Name
Get-aduser $ADUser
}
TIA
Andy
You can check whether Get-ADUser returns more than 0 objects to determine whether the GC satisfied your query. After that, use Set-ADServerSettings -PreferredGlobalCatalog to configure the preference
You will need to specify that you want to search the Global Catalog and not just the local directory. The Global Catalog is accessible from port 3268 on the DC, so it becomes something like:
$ForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$GCs = $ForestInfo.FindAllGlobalCatalogs()
Import-module ActiveDirectory
$ADUserName = "someusername"
$ADDomainDN = "DC=child,DC=domain,DC=tld"
$FinalGlobalCatalog = $null
foreach ($GC in $GCs)
{
$GCendpoint = "{0}:3268" -f $GC.Name
$SearchResult = Get-ADUser -LDAPFilter "(&(samaccountname=$ADUserName))" -Server $GCEndpoint -SearchBase $ADDomainDN -ErrorAction SilentlyContinue
if(#($SearchResult).Count -gt 0){
$FinalGlobalCatalog = $GC
break
}
}
if($FinalGlobalCatalog){
Write-Host "Found one: $($FinalGlobalCatalog.Name)"
Set-ADServerSettings -PreferredGlobalCatalog $FinalGlobalCatalog.Name
} else {
Write-Host "Unable to locate GC replica containing user $ADUserName"
}

Powershell: How do you set the Read/Write Service Principal Name AD Permissions?

In Powershell, how do you set the Read/Write Service Principal Name AD user permissions?
Normally during my build process, I use ADSIedit to navigate to that object, and then go through all the security tabs to get down to put a checkmark next to:
Read Service Principal Name
Write Service Principal Name
But navigating through ADSIedit can take a long time so I'm trying to script the process. If I have a PowerShell LDAP bind with a new user created, how can I use PowerShell to set both of these properties for this user account?
The following is a hacked out code-snippet of the possible pertinent portions of my install script:
$strDomain = "dc=my,dc=com"
$objDomain = [ADSI] "LDAP://" + strDomain
$strSCCMSQLPW = Read-Host -assecurestring "Please enter a password for the " + $strSCCMSQL + " account: "
New-ADUser -SamAccountName $strSCCMSQL + -Name $strSCCMSQL -AccountPassword $strSCCMSQLPW -Enabled $true -Path $strUsersOU + "," + $strDomain -PasswordNeverExpires $true
You need to add an ActiveDirectoryAccessRule object to the ACL of the target object. For setting property specific rigths the trick is to pass in the schemaIDGUID to the attribute. So first we need to find the schemaIDGUID from the Service-Principal-Name schema entry. In the sample code i statically refer to the Service-Principal-Name, better yet would have been to search for the ldapDisplayname to find the entry but I'm sure you can sort that out. In any case this code should do the job:
Function Set-SpnPermission {
param(
[adsi]$TargetObject,
[Security.Principal.IdentityReference]$Identity,
[switch]$Write,
[switch]$Read
)
if(!$write -and !$read){
throw "Missing either -read or -write"
}
$rootDSE = [adsi]"LDAP://RootDSE"
$schemaDN = $rootDSE.psbase.properties["schemaNamingContext"][0]
$spnDN = "LDAP://CN=Service-Principal-Name,$schemaDN"
$spnEntry = [adsi]$spnDN
$guidArg=#("")
$guidArg[0]=$spnEntry.psbase.Properties["schemaIDGUID"][0]
$spnSecGuid = new-object GUID $guidArg
if($read ){$adRight=[DirectoryServices.ActiveDirectoryRights]"ReadProperty" }
if($write){$adRight=[DirectoryServices.ActiveDirectoryRights]"WriteProperty"}
if($write -and $read){$adRight=[DirectoryServices.ActiveDirectoryRights]"readproperty,writeproperty"}
$accessRuleArgs = $identity,$adRight,"Allow",$spnSecGuid,"None"
$spnAce = new-object DirectoryServices.ActiveDirectoryAccessRule $accessRuleArgs
$TargetObject.psbase.ObjectSecurity.AddAccessRule($spnAce)
$TargetObject.psbase.CommitChanges()
return $spnAce
}
Sample lines for calling the function...
$TargetObject = "LDAP://CN=User,OU=My User Org,DC=domain,DC=net"
$Identity = [security.principal.ntaccount]"domain\user"
Set-SpnPermission -TargetObject $TargetObject -Identity $Identity -write -read
Here is an example using Quest to set the permissions on the service principal name attributes.
First, add Quest:
Add-PSSnapin Quest.ActiveRoles.ADManagement;
Set the permission (using Add-QADPermission):
Get-QADUser UserName | Add-QADPermission -Account 'SELF' -Rights 'ReadProperty,WriteProperty' -Property 'servicePrincipalName' -ApplyTo 'ThisObjectOnly';
You can use Quest AD cmdlets. It makes AD permission stuff very easy in PowerShell.
Read this blog for some examples on how to add AD permissions or even copy the AD permissions.
Just lookup Add-QADPermission and it should do your job.