ADFS Powershell script to add additional SamlEndpoints to existing - powershell

This question likely doesn't require actual knowledge of ADFS, but I'm providing that for context. The command "Set-AdfsRelyingPartyTrust -Name X -SamlEndpoint Y" overwrites all SAML endpoints with what you specify. What I'd like to do is create a script that takes the existing SAML endpoints and sets them as variables so that I can then add them all back along with the new endpoint.
If there's only one existing endpoint, I can put it into a variable using this and it works:
$EP = New-AdfsSamlEndpoint -Binding "POST" -Protocol "SAMLAssertionConsumer" -Uri "https://test.com" -Index 1
$EP1 = Get-ADFSRelyingPartyTrust -Name "X" | Select-Object -ExpandProperty SamlEndpoints
Set-AdfsRelyingPartyTrust -TargetName "PsTest" -SamlEndpoint $EP,$EP1
The problem with this is that, if multiple endpoints exist, expand-property returns them all as a single value which breaks the function. Using "-limit 1" doesn't work because the whole output of expand-property is considered 1.
What I can do is to generate a numbered list of each index value using this command:
Get-AdfsRelyingPartyTrust -Name "X" | Select-Object -ExpandProperty SamlEndpoints | Select-Object -ExpandProperty Index
and then create a unique variable for each corresponding index value
$EP1 = Get-ADFSRelyingPartyTrust -Name "X" | Select-Object -ExpandProperty SamlEndpoints | Where-Object {$_.Index -eq 2}
But in order to completely script this rather than setting variables by hand, I'd need automate setting "$_.Index -eq" to each index value that's output from "-ExpandProperty Index", and to assign a unique variable to each of those, which is where I'm stuck. What's the best way to approach this?

I don't have access to these command so I am having to guess a little here, but it looks like your command
Set-AdfsRelyingPartyTrust -TargetName "PsTest" -SamlEndpoint $EP,$EP1
accepts an array for the -samlEndpoint parameter.
What I would do it work with the arrays like so.
$EP = New-AdfsSamlEndpoint -Binding "POST" -Protocol "SAMLAssertionConsumer" -Uri "https://test.com" -Index 1
$EndPoints = #(Get-ADFSRelyingPartyTrust -Name "X" | Select-Object -ExpandProperty SamlEndpoints)
$Endpoints += $EP
Set-AdfsRelyingPartyTrust -TargetName "PsTest" -SamlEndpoint $EndPoints

Related

How do you make a foreach create multiple variables?

I'm trying to make a script that will check a bitlocker encryption percentage and set it as a variable but for each drive.
For example. I run my script that will begin bitlocker encryption on 2 drives. I need a foreach statement that will create a variable foreach drive (2), then I will have an while statement that says while a drive is less than 100 percent, recheck the percentage. Than once it hits 100 percent it will lock the drive.
I don't know how to write code so that I can have it create a while statement for each drive that is detected and has started encrypting.
Sorry if this is confusing I'm new.
Heres my code so far. (DO NOT RUN ON YOUR COMPUTER BECAUSE IT WILL BITLOCK YOUR DRIVES lol, IF YOU WANT TO RUN IT DELETE THE "Enable-Bitlocker" CODE)
#Finds all drives connected to the computer, I'm not sure if this will also detect CDs though, but if it does let me know since they can't be bitlocked
$Drives = Get-BitLockerVolume | Where-Object -Property "MountPoint" | Select-Object -ExpandProperty "MountPoint"
#Encrypts the password so it can't be displayed with a read-host command
$SecureString = ConvertTo-SecureString "test" -AsPlainText -Force
#This will run the bitlocker command and encrypt each drive with the secure password.
foreach ($Drive in $Drives){
Enable-BitLocker -MountPoint $Drive -EncryptionMethod Aes256 -Password $SecureString -PasswordProtector -SkipHardwareTest -WhatIf #WhatIf added for safety measures
}
#this is supposed to make a new variable for each drive to individually track percentages
foreach ($Drive in $Drives){
$Percentage = Get-BitLockerVolume | Where-Object -Property "EncryptionPercentage" | Select-Object -ExpandProperty "EncryptionPercentage"
}
#this will continually recheck the percentage until it gets to 100 percent but I need to make it so it will have a while statement for each percentage variable created.
while ($Percentage -LT 100) {
$Percentage = Get-BitLockerVolume | Where-Object -Property "EncryptionPercentage" | Select-Object -ExpandProperty "EncryptionPercentage"
sleep -Seconds 10
}
Get-BitLockerVolume only returns valid target volumes, so I wouldn't be too worried about optical drives.
You don't need to create 2 variables to store the percentages - simply use Where-Object to test whether any volume is still under 100, then sleep:
# Fetch any drives that aren't already fully encrypted
$unencryptedVolumes = Get-BitLockerVolume | Where-Object VolumeStatus -ne FullyEncrypted
$SecureString = ConvertTo-SecureString "test" -AsPlainText -Force
# Encrypt each volume with the give password.
$unencryptedVolumes |Enable-BitLocker -EncryptionMethod Aes256 -Password $SecureString -PasswordProtector -SkipHardwareTest
# Keep sleeping until all volumes are fully encrypted
while ($waitingFor = Get-BitLockerVolume | Where-Object VolumeStatus -ne FullyEncrypted) {
# Print list of in-progress volumes to the screen, sleep
$waitingFor |Format-Table MountPoint,VolumeStatus,EncryptionPercentage
Start-Sleep -Seconds 10
}
To answer your question as stated, you want to use set-variable. or get-variable.
This command will store/retrieve variables, and the variables can be named via another variable. This get's tricky, and in order to do math you need to combine them.
for example
Foreach ($number in 1..20) {
Set-Variable -Name name$number -Value "this is number $number"
Get-Variable -Name name$number
}
you can also use Remove-Variable or New-Variable
but New-Variable is basically superseded by Set-Variable as well as, if a variable exists, New-Variable will spit out an error
in order to do math it get's trickier as you can't just call the variable and add 1
so this is very not effective but something like:
foreach ($number in 1..20) {
Set-Variable -Name name$number -Value "$((get-variable -name name$number) + 1)"
Get-Variable -Name name$number
}
this could increment it by 1. but now you're calling a ton of stuff to do a simple name1+=1 or name1++
you can also do some fancy stuff like
Foreach ($number in 1..20) {
Set-Variable -Name "name$({0:D2} -f $number)" -Value "this is number $number"
Get-Variable -Name "name$({0:D2} -f $number)"
}
the more you have, the more you have to code and get (like calling the variable as above is a long string). As the other answer says, usually there's a much simpler option.

Running ForEach-Object -Parallel, data missing from export

I have some working code that basically queries 2 different Graph API endpoints, then searches for a match in the User Principal Name column, and inserts the extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber column and values to the exported csv (Thanks to the user #PMental for this solution) This column derives from attribute that was recently extended from our on premises AD.
This code works perfectly fine, however if I try to parallelize it, I get no results in the extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber column.
Is this because once it is being parallelized, I'm not able to share variables between the parallel processes? If so, how on earth do I accomplish this?
Code below - if you remove the -Parallel, it works fine:
$graphApiUri = "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D90')"
$Uri = "https://graph.microsoft.com/v1.0/users?`$select=userPrincipalName,extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber"
$O365Report = Invoke-RestMethod -Method Get -Uri $graphApiUri -Headers $headerParams | ConvertFrom-Csv
# If the result is more than 999, we need to read the #odata.nextLink to show more than one side of users
$UserDetails = while (-not [string]::IsNullOrEmpty($uri)) {
# API Call
$apiCall = try {
Invoke-RestMethod -Headers $headerParams -Uri $uri -Method Get
}
catch {
$errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
}
$uri = $null
if ($apiCall) {
# Check if any data is left
$uri = $apiCall.'#odata.nextLink'
$apiCall
}
}
Write-Output "Matching UPN to employeeNumber..."
$O365Report | ForEach-Object -Parallel {
$CurrentEmpNumber = $UserDetails.value |
Where-Object userPrincipalName -eq $_.'User Principal Name' |
Select-Object -ExpandProperty extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber -ErrorAction SilentlyContinue
$_ | Add-Member -MemberType NoteProperty -Name extension_335d4df9847945fbaa472c8b8fbb5d75_employeeNumber -Value $CurrentEmpNumber
}
$O365Report | Export-Csv $ReportCSV -NoTypeInformation
Write-Output "Report saved to $ReportCSV."
When inside of a ForEach-Object -Parallel script block, and you are trying to reference variables which were created outside of it, you need to preface the variable name with using: so it would be $using:UserDetails
Examples:
Returns nothing because $test isn't accessible within the scope of the parallel script block:
$test = 1;
0..5 | % -Parallel { $test; };
Returns the value of $test five times because by using $using:test you are now able to see its value:
$test = 1;
0..5 | % -Parallel { $using:test; };
From documenation:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7.1
The ForEach-Object -Parallel parameter set runs script blocks in parallel on separate process threads. The $using: keyword allows passing variable references from the cmdlet invocation thread to each running script block thread. Since the script blocks run in different threads, the object variables passed by reference must be used safely. Generally it is safe to read from referenced objects that don't change. But if the object state is being modified then you must used thread safe objects, such as .Net System.Collection.Concurrent types (See Example 11).
Personal note:
I would also recommend using -ThrottleLimit to limit its max degrees of paralellism. The default is 5, but you may want more or less than that depending on testing.

Extracting part of a host name with select-object

I have a simple powershell function where I provide the log type and event and it scans all of our SQL servers. it works except the host name is returned as hostname.domain.local. I want it to return just the host name. I've tried machinename.split('.') and substring and it won't work. I've tried putting the select-object into a separate variable and was going to join it with the rest of the columns, but it takes too long to run.
Here is my sample scrap code i'm testing with before I change my function along with the commented out parts that didn't work. Looked around and found lots of resources about the commands, but they don't work when I try to use them in my script.
The error I keep getting is A positional parameter cannot be found that accepts argument '. '.
$servers = Get-Content -literalpath "C:\temp\sql_servers3.txt"
#$server
#$result =
ForEach($box in $servers) {Get-Eventlog -ComputerName $box -LogName
application -After 1-4-2018 -Entrytype Error | Where {$_.source -notin
'Perfnet','Perflib', 'ntfs', 'vss'}| select-object -property MachineName}
#$result_Host_name = select-object -inputobject $result -property
'MachineName'
#'TimeGenerated', 'MachineName'.Split('.')[1], 'EventID','message'}
#| Where {$_.source -notin 'Perfnet','Perflib', 'ntfs', 'vss'} 0
#return $result_Host_name
What you are looking for is a "Calculated Property" when using Select-Object.
| Select-Object #{n='HostName';e={($_.MachineName -split '\.')[0]}}

Indirect reference to a object member is powershell

I have an object :
Get-ItemProperty -Path ("Registry:"+$my_registry_key) -Name $my_entry
But I still have several useless properties. I would like to compare wath is expected to get out (a string) to zero.
Since % operator is maybe a bit too magical... I would like to expend it to make an indirect reference to property instead of a direct (or hard-coded one). What does happens if property does not exists?
( `
( Get-ItemProperty -Path ("Registry:$my_registry_key") `
| need_some_magic -property $my_entry`
) -ne 0 `
)
And expect one boolean (either $false or $true).
Does Powershell have some kind of hash which can be retrieved through variable instead of $_.property.
You can do the following:
Get-ItemProperty -Path ("Registry:"+$my_registry_key) | select -ExpandProperty $my_entry
So:
if($(Get-ItemProperty -Path ("Registry:"+$my_registry_key) | select -ExpandProperty $my_entry) -ne 0) {
...
}

Out-file format

I am writing a script that after each iteration through a loop (array of selected services) it will gather the 4 values for each service that are: server name, service name, service state, and service start name
So for each iteration, I would like to output the 4 mentioned values to an external file (txt, svc, or html) such that each value will be arranged in its own column. Currently I use tab `t to arrange the values in each column but it doesn't work quite well because some service name is a lot longer or a lot shorter so it screws up the column alignment. What other approach do you suggest so all columns are aligned properly
Below is a snippet of my script on how I currently format the output to a txt file
ForEach($service in services)
$startname = $service.startname
$state = $service.state
$servicename = $service.name
write-output "$server `t $servicename `t $state `t $startname is current" | out-file -append $ScriptDirectory
If you just want to dump the results to text in a nicely-formatted way (i.e. you don't have requirements for making this CSV, or tab-delimited, or anything else besides "easy for a person to read"), then just use Format-Table -AutoSize.
AutoSize does exactly what you want - it inspects the length of all properties you are outputting, then dynamically adjusts the column width so that as much as possible is shown.
You don't explain where $server comes from, I will assume that is defined somewhere else...
$services `
| Format-Table -AutoSize #{N='Server';E={$server}},StartName,State,Name `
| Out-String `
| Out-File results.txt
Instead of using several variables, use a Powershell object to store your output. Something like this:
ForEach($service in $services) {
New-Object PSObject -Property #{
StartName = $service.startname
State = $service.state
ServiceName = $service.name
}
} | Out-File $ScriptDirectory
You may need to add a Select-Object in the chain to ensure the columns are in the correct order that you want for your final output.
If you want to keep the variables, You could try the following String formatting to space out the variable in the string evenly. In the example below the spacing is 20 characters between each value:
ForEach($service in services){
$startname = $service.startname
$state = $service.state
$servicename = $service.name
"{0,-20} | {1,-20} | {2,-20} | {3,-20}" -f $server,$servicename,$state,$startname `
| Out-File -append $ScriptDirectory
}
It's a little unclear what you're looking for as some of the properties of the object Get-Service returns don't exist as written and the code seems incomplete. Taking a guess at your intent though:
$servers = #("server1","server2");
$services = get-service -computername $servers;
$svcCollection = #();
ForEach($service in $services) {
$svccollection+=New-Object PSObject -Property #{
Servername = $service.MachineName;
StartName = $service.servicename;
State = $service.Status;
ServiceName = $service.DisplayName;
}
}
# Various output formats
$svccollection|ConvertTo-Html|Out-File -path Services.html; # Create a full HTML file
$svcCollection|Export-Csv -NoTypeInformation -Path Services.csv; # Create a "traditional" CSV file
$svcCollection|Export-Csv -Delimiter "`t" -Path Services-tab.csv; # Create a tab-delimited CSV file
$svcCollection|ConvertTo-Xml|Out-File -path Services.xml; # Create an XML file
$svcCollection|ConvertTo-Json|Out-File -path Services.js; # Create a JSON object (v3 only)