Foreach loop in a if statement - powershell

Wrote a small script to check if certain AD groups exists. For some reason it wont loop through the given array. It only writes the first value of the array to the console. When I put a breakpoint on:
foreach ($item in $SecGroupNames)
I see $secGroupNames being filled with the gives values, can anyone help me out? Can't figure it out for this one.
Code:
Import-Module activedirectory
$SecGroupNames = #(
"DB_DATAREADER",
"DB_DATAWRITER",
"DB_OWNER",
"SQL_Public",
"SQL_SA",
"SQL_SecurityAdmin"
)
foreach ($item in $SecGroupNames)
{
If (Get-ADGroup $item)
{
Write-Host -ForegroundColor Yellow "$item Exists!"
return $true;
}
else
{
Write-Host -ForegroundColor Green "$Item Does not exist, Do something!"
return $false;
}
}
Output:
PS C:\Scripts\CreateOUgroups> C:\Scripts\CreateOUgroups\FunctionCheckSecurityGroup.ps1
DB_DATAREADER Exists!
True

It's because of return statements. It causes the script to return value and end execution in the first loop pass.
If you want to return multiple values from script or function, use Write-Output instead of return.
foreach ($item in $SecGroupNames)
{
if (Get-ADGroup $item)
{
Write-Host -ForegroundColor Yellow "$item Exists!"
Write-Output $true;
}
else
{
Write-Host -ForegroundColor Green "$item Does not exist, Do something!"
Write-Output $false;
}
}

Related

Powershell - Azure licence based on ad group

I have been developing AzureAD licence script based on AD Group. So, Find users with a direct assigned, find them in AD, evaluate what group they should be a member of, add them to licensing group. I have hashtable with multiple values $SKUToGroupRev. I can not match hashtable with multiple values with if($ADGroup = $SKUToGroupRev[$SKU.SkuId]) .
From what I want to do :
if there are 18181a46-0d4e-45cd-891e-60aabd171b4e and 0c266dff-15dd-4b49-8397-2bb16070ed52 inside SKUs variable for below command then I will add AD group related to the inside hashtable such as O365_E1_Users
OR
if there are 6fd2c87f-b296-42f0-b197-1e91e994b900 and 0c266dff-15dd-4b49-8397-2bb16070ed52 inside SKUs variable for below command then I will add AD group related to the inside hashtable such as O365_E3_Users
e.g:
# Get licensed SKUs for the user
$aaduser = get-azureaduser -objectID $user.UserPrincipalName
$SKUs = $aaduser | Select UserPrincipalName,ImmutableID -ExpandProperty AssignedLicenses
e.g output:
UserPrincipalName ImmutableId DisabledPlans SKUId
----------------- ----------- ------------- -------------
User01#contoso.com x+MVG6EKEUWHi3r6zjgzCA== {041fe683-03e4-45b6-b1af-c0cdc516da4f... 6fd2c87f-b296-42f0-b197-1e91e994b900
User01#contoso.com x+MVG6EKEUWHi3r6zjgzCA== {} 0c266dff-15dd-4b49-8397-2bb16070ed52
Here is my script :
$CSVfile = "C:\temp\LicenseToGroupUsers.csv"
# Import the CSV file
try {
$users = import-csv $CSVfile
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
Break
}
write-warning "About to add the following users to license groups for complete SKU:"
foreach ($user in $users){
write-host $user.UserPrincipalName
}
Read-Host -Prompt "Press Enter to continue or CTRL+C to quit"
$e3 = -split "0c266dff-15dd-4b49-8397-2bb16070ed52 6fd2c87f-b296-42f0-b197-1e91e994b900"
$e1 = -split "18181a46-0d4e-45cd-891e-60aabd171b4e 0c266dff-15dd-4b49-8397-2bb16070ed52"
$TEAMS_EXPLORATORY = -split "710779e8-3d4a-4c88-adb9-386c958d1fdf 0c266dff-15dd-4b49-8397-2bb16070ed52"
#$FLOW_FREE_E3 = -split "f30db892-07e9-47e9-837c-80727f46fd3d 6fd2c87f-b296-42f0-b197-1e91e994b900 0c266dff-15dd-4b49-8397-2bb16070ed52"
foreach ($user in $users){
$groupsToAdd = #()
$groupsToRemove = #()
write-host "Processing" $user.UserPrincipalName
# Get licensed SKUs for the user
$aaduser = get-azureaduser -objectID $user.UserPrincipalName
#$SKUs = $aaduser | Select UserPrincipalName,ImmutableID -ExpandProperty AssignedLicenses
#Get the AD ObjectGuid for the group add (cannot use UPN)
$ImmutableID = "" #Null these out otherwise gets reused from previous
#Have to match using the guid
$ImmutableID = $aaduser.ImmutableID
if ($ImmutableID) {$objectGUID = ([GUID][System.Convert]::FromBase64String($ImmutableID)).Guid}
else {
write-warning "Error getting ImmutableID for $UPN, user is likely cloud only, skipping"
Break
}
# test 1
$licenses = $aaduser.AssignedLicenses.SkuId
$is_e1 = !($e1 | ForEach-Object { $licenses.Contains($_) }).Contains($false)
if($is_e1 -eq "True"){
try {
write-host "Adding" $user.UserPrincipalName"to E1Group" -ForegroundColor Green
Write-Host "Test 1: $is_e1"
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
}
}
$is_e3 = !($e3 | ForEach-Object { $licenses.Contains($_) }).Contains($false)
if($is_e3 -eq "True"){
try {
write-host "Adding" $user.UserPrincipalName"to E3Group" -ForegroundColor Green
Write-Host "Test 3: $is_e3"
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
}
}
$is_TEAMS_EXPLORATORY = !($TEAMS_EXPLORATORY | ForEach-Object { $licenses.Contains($_) }).Contains($false)
if($is_TEAMS_EXPLORATORY -eq "True"){
try {
write-host "Adding" $user.UserPrincipalName"to (TEAMS_EXPLORATORY)E1Group" -ForegroundColor Green
Write-Host "Test 1: $is_TEAMS_EXPLORATORY"
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
}
}
<# $is_FLOW_FREE_E3 = !($FLOW_FREE_E3 | ForEach-Object { $licenses.Contains($_) }).Contains($false)
if($is_FLOW_FREE_E3 -eq "True"){
try {
write-host "Adding" $user.UserPrincipalName"to (FLOWFREE)E3Group" -ForegroundColor Green
Write-Host "Test 1: $is_FLOW_FREE_E3"
}
catch {
$errorZero = $Error[0]
write-host "Error: " $errorZero -ForegroundColor Red #Writes the latest error
}
}#>
}
To test agains a combination of SkuID's, using a lookup hashtable as in your first approach is not the easiest way I think. Your current approach looks much better to me, only I would not put the ID's in array variables, but test them literally against the ID's as they are found in the users AssignedLicenses.
Something like this:
$CSVfile = "C:\temp\LicenseToGroupUsers.csv"
# Import the CSV file
$users = Import-Csv -Path $CSVfile
Write-Warning "About to add the following users to license groups for complete SKU:"
$users.UserPrincipalName -join [environment]::NewLine
Write-Host
$answer = Read-Host -Prompt "Press Enter to continue or Q to quit"
if ($answer[0] -eq 'Q') { Clear-Host; exit }
foreach ($user in $users) {
Write-Host "Processing" $user.UserPrincipalName
$ImmutableID = $null # Null these out
$ADGroup = $null
# Get licensed SKUs for the user
$aaduser = Get-AzureADUser -objectID $user.UserPrincipalName
# Get the AD ObjectGuid for the group add (cannot use UPN)
# Have to match using the guid
$ImmutableID = $aaduser.ImmutableID
if (![string]::IsNullOrWhiteSpace($ImmutableID)) {
$objectGUID = ([GUID][System.Convert]::FromBase64String($ImmutableID)).Guid}
else {
Write-Warning "Error getting ImmutableID for $($user.UserPrincipalName), user is likely cloud only, skipping"
continue # skip this one and proceed with the next user
}
$licenses = #($aaduser.AssignedLicenses.SkuId) # force it to be an array
##########################################################################################
# Apparently, SkuId '0c266dff-15dd-4b49-8397-2bb16070ed52' is needed for all combinations,
# so we could already rule out users that do not have that ID in their $licenses..
# if that is indeed the case, you can simplify al other tests by not having to check
# for this ID every time..
# for now, this is an assumption, so commented out.
# if (!($licenses -contains '0c266dff-15dd-4b49-8397-2bb16070ed52')) {
# Write-Warning "Could not determine a group for user $($user.UserPrincipalName)"
# continue # skip this one and proceed with the next user
# }
##########################################################################################
# test E1: 'Microsoft 365 Audio Conferencing' and 'OFFICE 365 E1'
if ($licenses -contains '0c266dff-15dd-4b49-8397-2bb16070ed52' -and
$licenses -contains '18181a46-0d4e-45cd-891e-60aabd171b4e') {
# Add this user to group 'O365_E1_Users'
$ADGroup = 'O365_E1_Users'
}
# test E3: 'Microsoft 365 Audio Conferencing' and 'OFFICE 365 E3'
elseif ($licenses -contains '0c266dff-15dd-4b49-8397-2bb16070ed52' -and
$licenses -contains '6fd2c87f-b296-42f0-b197-1e91e994b900') {
if ($licenses -contains 'f30db892-07e9-47e9-837c-80727f46fd3d') { # also 'MICROSOFT FLOW FREE' ?
# Add this user to group 'FLOW_FREE_E3'
$ADGroup = 'FLOW_FREE_E3'
}
else {
# Add this user to group 'O365_E3_Users'
$ADGroup = 'O365_E3_Users'
}
}
# test 'Microsoft 365 Audio Conferencing' and 'MICROSOFT TEAMS EXPLORATORY'
elseif ($licenses -contains '0c266dff-15dd-4b49-8397-2bb16070ed52' -and
$licenses -contains '710779e8-3d4a-4c88-adb9-386c958d1fdf') {
# Add this user to group 'TEAMS_EXPLORATORY'
$ADGroup = 'TEAMS_EXPLORATORY'
}
# finished the conditions, now see if we can add the user to one of the groups
if (![string]::IsNullOrWhiteSpace($ADGroup)) {
try {
Write-Host "Adding $($user.UserPrincipalName) to $ADGroup" -ForegroundColor Green
# Add-ADGroupMember -Identity $ADGroup -Members $objectGUID
}
catch {
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
}
}
else {
Write-Warning "Could not determine a group for user $($user.UserPrincipalName)"
}
}

How do I delete user in Active Directory through Powershell?

I have to delete users from my AD through Powershell. Powershell has to ask me who I want to delete, once I type in the username it should delete the account. Also when Powershell succesfully deleted the account or it should give me a message.
I'm kind of noob at this, but here is my code:
function aduser-remove($userremove){
Remove-ADUser -Identity $delete
if ($delete -eq $userremove){
return $true
}
else {
return $false
}
}
$delete = Read-host "Which user do you want to delete? (Type in username)."
aduser-remove $delete
if ($userremove -eq $true){
Write-Host $delete "deleted succesfully!" -ForegroundColor Green
}
elseif ($userremove -eq $false){
Write-Host "An error occured by deleting" $delete -ForegroundColor Red
}
else {
Write-Host $delete "does not exist." -ForegroundColor DarkGray
}
The result here is that Powershell does ask if I want to delete the account and it works. But Powershell keeps giving me the else message instead of the if message. Deleting the account was succesfull.
I have no idea what to now or if I'm missing something (I bet I am otherwise it would work).
I hope you guys can help me!
As commented, your code uses variables in places where they do not exist.
Also, I would recommend trying to find the user first and if you do, try and remove it inside a try/catch block, as Remove-ADUser creates no output.
Below a rewrite of your code. Please note that I have changed the name of the function to comply with the Verb-Noun naming convention in PowerShell.
function Remove-User ([string]$userremove) {
# test if we can find a user with that SamAccountName
$user = Get-ADUser -Filter "SamAccountName -eq '$userremove'" -ErrorAction SilentlyContinue
if ($user) {
try {
$user | Remove-ADUser -ErrorAction Stop -WhatIf
return $true
}
catch {
return $false
}
}
# if we get here, the user does not exist; returns $null
}
$delete = Read-host "Which user do you want to delete? (Type in username)."
# call your function and capture the result
$result = Remove-User $delete
if ($result -eq $true){
Write-Host "User $delete deleted succesfully!" -ForegroundColor Green
}
elseif ($result -eq $false){
Write-Host "An error occured while deleting user $delete" -ForegroundColor Red
}
else {
Write-Host "$delete does not exist." -ForegroundColor DarkGray
}
Note also that I have put in the -WhatIf switch. This switch ensures you will only get a message of what would happen. No user is actually deleted. Once you are satisfied the code does what you want, remove the -WhatIf switch.
Hope that helps
If you have rsat installed https://www.microsoft.com/en-us/download/details.aspx?id=45520
remove-aduser https://learn.microsoft.com/en-us/powershell/module/addsadministration/remove-aduser?view=win10-ps

Dealing with Arrays in Powershell (testing with Pester)

I have a little trouble understanding the way to implement this process. I want to achieve a total count in the score so that if a test successfully passes or fails it can be added into an array. That array will be counted in the length.
This is my code as an example:
#This stores the array of the number of passed and failed test
$passed = #()
$failed = #()
Describe "Template Syntax" {
It "Has a JSON template" {
$fileLocation = "$here\azuredeploy.json"
$fileCheck = $fileLocation | Test-Path
if ($fileCheck -eq $true) { $passed = $passed + 1
Write-Host "1st file exist " }
if ($fileCheck -eq $false) { $failed = $failed + 1
Write-Host "1st file does exist" }
}
It "Has a parameters file" {
$fileLocation ="$here\azuredeploy.parameters*.json"
$fileCheck = $fileLocation | Test-Path
if ($fileCheck -eq $true) { $passed = $passed + 1;
Write-Host "2nd file exist "}
if ($fileCheck -eq $false) { $failed = $failed + 1
Write-Host "2nd file does exist" }
}
PrintArgs
}
function PrintArgs(){
Write-Host -ForegroundColor yellow "Passed: $($passed.Length) Failed: $($failed.Length)"
}
Is there a different way or another approach that I can do to achieve this? I know that pester does it automatically, however, I want to use a Powershell script to test.
Your Pester tests aren't really Pester tests unless you include a Should assertion. I think you should rewrite your tests as follows:
Describe "Template Syntax" {
$fileLocation = "$here\azuredeploy.json"
$fileCheck = $fileLocation | Test-Path
It "Has a JSON template" {
$fileCheck | Should -Be $true
}
$fileLocation ="$here\azuredeploy.parameters*.json"
$fileCheck = $fileLocation | Test-Path
It "Has a parameters file" {
$fileCheck | Should -Be $true
}
}
If you then run this with Invoke-Pester you get a summary of passed and failed test counts at the end automatically. If you need to access these values, you can use -PassThru to return them to a variable. For example:
$Results = Invoke-Pester .\your.tests.ps1 -PassThru
Then you can get the number of passed and failed tests as follows:
$Results.PassedCount
$Results.FailedCount
If you genuinely want to use your own counters (which would mean maintaining lots of unnecessary logic) you could do as follows:
$Passed = 0
$Failed = 0
Describe "Template Syntax" {
$fileLocation = "$here\azuredeploy.json"
$fileCheck = $fileLocation | Test-Path
It "Has a JSON template" {
$fileCheck | Should -Be $true
}
if ($fileCheck -eq $true) { $Passed++ } else { $Failed++ }
$fileLocation ="$here\azuredeploy.parameters*.json"
$fileCheck = $fileLocation | Test-Path
It "Has a parameters file" {
$fileCheck | Should -Be $true
}
if ($fileCheck -eq $true) { $Passed++ } else { $Failed++ }
Write-Host -ForegroundColor yellow "Passed: $Passed Failed: $Failed"
}
Note that the Describe block acts as a kind of script scope, so you have to print the values of $Passed and $Failed within the Describe to get at the values.
Looking at your code, you do not need arrays to count the scores.
Instead of defining $passed and $failed as arrays, just set them up as integer counters with a starting value of 0
$passed = $failed = 0
Then instead of calling function PrintArgs() you simply do
Write-Host -ForegroundColor yellow "Passed: $passed Failed: $failed"
By the way, to increment a counter you can simply do $passed++ instead of $passed = $passed + 1
If you DO insist on using arrays you can change the $passed = $passed + 1 to something like $passed += $true. By doing that you add a new element to the array with a value of $true (or whatever you feel is more appropriate.

Checking for multiple conditions without a break in Powershell

I am working on a validation script for a Domain Controller. I have an account that has 4 things that I need to verify. The code that I have works but I don't want it to break if one of the conditions aren't met and that is what it is doing now. So, basically I'm needing it to check all 4 criteria, regardless if the condition is met or not.
Here is my code:
if(net user "user.dadm") {
Write-Host "[√] User.dadm was created successfully" -fore GREEN
if((((uACADA "user.dadm") -band 65536) -ne 0) -eq $true) {
Write-Host "[√] Password for user.dadm is set to never expire" -fore GREEN
if((Get-ADGroupMember -Identity "Domain Admins").Name -contains "user.dadm") {
Write-Host "[√] User.dadm was added to Domain Admins" -fore GREEN
if((((uACADA "user.dadm") -band 1048576) -ne 0) -eq $true) {
Write-Host "[√] Account Delegation Authority was removed" -fore GREEN
} else {
Write-Host "[X] Account Delegation was not removed" -fore RED
}
} else {
Write-Host "[X] User.dadm was not added to Domain Admins" -fore RED
}
} else {
Write-Host "[X] Password for user.dadm has been set to expire" -fore RED
}
} else {
Write-Host "[X] User.adam was not created" -fore RED
}
What this does is it will break to the else statement if any of the conditions aren't met but I need it to continue checking each condition.
I know that I could break this up and check each condition individually but I prefer to have it as compressed as possible as it is already going to be over a thousand lines.
I guess the REAL question is if this can be done without making 4 separate IF/ELSE statements.
I'm not sure if its a structuring issue (nested IF) or a command issue (using IF and ELSE when I should use IF and ELSEIF).
Could someone point me in the right direction? Thanks in advance.
not tested, but this may be a starting point, though you may just choose the simplicity of multiple if statements.
$user = 'user.dadm'
$info = [ordered]#{
created = "[X] $user was not created"
neverexp = "[X] Password for $user has been set to expire"
admin = "[X] $user was not added to Domain Admins"
delegation = "[X] Account Delegation for $user was not removed"
}
switch ($true) {
{net user $user} {
$info.created = "[√] $user was created successfully"
}
{((uACADA $user) -band 65536) -ne 0} {
$info.neverexp = "[√] Password for $user is set to never expire"
}
{(Get-ADGroupMember -Identity 'Domain Admins').Name -contains $user} {
$info.admin = "[√] $user was added to Domain Admins"
}
{((uACADA $user) -band 1048576) -ne 0} {
$info.delegation = "[√] Account Delegation Authority for $user was removed"
}
default {Write-Host 'all are false'}
}
$info.Keys | % {
if ($info.$_.startswith('[X]')) {
Write-Host $($info.$_) -ForegroundColor Red
} else {
Write-Host $($info.$_) -ForegroundColor Green
}
}
Yes, you can write it without if tests, e.g.
$colors = #('Red', 'Green')
$messages = #('[X] Failed -', '[√] Succeeded -')
$username = "user.adm"
$result = [bool](net user "$username")
Write-Host "$($messages[$result]) Check user account exists" -ForegroundColor $colors[$result]
$result = [bool]((uACADA "$username") -band 65536)
Write-Host "$($messages[$result]) Check password never expires" -ForegroundColor $colors[$result]
$result = [bool]((Get-ADGroupMember -Identity "Domain Admins").Name -contains "$username")
Write-Host "$($messages[$result]) Check account is a domain admin" -ForegroundColor $colors[$result]
$result = [bool]((uACADA "$username") -band 1048576)
Write-Host "$($messages[$result]) Check Delegation Authority removed" -ForegroundColor $colors[$result]
NB. that if you aren't using if/else, you need some other way to do the true/false testing. I'm casting results to [bool] and using 2-element arrays. $array[$false] casts $false -> 0 and gets element 0, $array[$true] casts $true -> 1 and gets element 1. That's how the result is turned into the appropriate colors and messages.
Another reasonable way to write this would be to move the Write-Hosts into a function.
Function Report-Result {
param($Result, $Text)
if ($Result) {
Write-Host "Success - $Text" -Fore Green
} else {
Write-Host "Failure - $text" -Fore Red
}
}
$result = [bool](...)
report-result $result "Check password never expires"
... etc.
Output looks like:
Which is okayyyy - it gets your 20 lines of code down to ~10, and has no nesting, and runs all the tests.
But it really feels like you're re-inventing PowerShell DSC ("my desired state is that accounts with .adm at the end have their passwords set to never expire") or Pester - PowerShell's test framework, ("test that all .adm accounts have passwords set to never expire").
I'm not sure that you can exactly fit Pester into your use case, but it makes me think I would change everything about the script from the way it's structured to the output messages, to make it look and feel like Pester. Particularly I want clear separation of test definitions and printed output, e.g.:
1. Here are my tests, with self-explanatory names
2. Here is a loop which runs all tests, and reports on them
and that gives me something like:
Function Validate-DCUserAccountShouldExist {
param($Username) [bool](net user "$UserName")
}
Function Validate-DCUserAccountPasswordNeverExpireShouldBeSet {
param($Username) [bool]((uACADA "$username") -band 65536)
}
Function Validate-DCUserAccountShouldBeADomainAdmin {
param($Username) [bool]((Get-ADGroupMember -Identity "Domain Admins").Name -contains "$username")
}
Function Validate-DCUserAccountShouldHaveDelegationAuthorityRemoved {
param($Username) [bool]((uACADA "$Username") -band 1048576)
}
# Search for all Validation functions and run them
$tests = (gci function: |Where Name -Like 'Validate-DCUserAccount*').Name
foreach ($test in $tests) {
$result = try {
& $test "user.adm"
} catch {
$false
}
if ($result) {
Write-Host -ForegroundColor Green "[√] $test - succeeded"
} else {
Write-Host -ForegroundColor Red "[X] $test - Failed"
}
}
Which gives an output like:
My view of my second code is that:
It's longer
more complex, less easy to follow
a lot less duplication of Write-Host
more structured, separation of test definitions and output
function names explain what the state should be, which makes the output messages work for success/failure
Output messages are neater in the code, but uglier to read in the output (but could be adjusted, e.g. add spaces when printing them)
would scale to more tests nicely, just by adding more tests

powershell Foreach-Object usage

I have script:
$servers = "server01", "s02", "s03"
foreach ($server in $servers) {
$server = (New-Object System.Net.NetworkInformation.Ping).send($servers)
if ($server.Status -eq "Success") {
Write-Host "$server is OK"
}
}
Error message:
An exception occured during a Ping request.
I need to ping each server in $servers array and display status. I think, that Foreach statement is not properly used, but I'm unable to find out where is the problem. Thank you for your advice
You should not be modifying the value of $server within the foreach loop. Declare a new variable (e.g. $result). Also, Ping.Send takes the individual server name, not an array of server names as an argument. The following code should work.
Finally, you will need to trap the PingException that will be thrown if the host is unreachable, or your script will print out a big red error along with the expected results.
$servers = "server1", "server2"
foreach ($server in $servers) {
& {
trap [System.Net.NetworkInformation.PingException] { continue; }
$result = (New-Object System.Net.NetworkInformation.Ping).send($server)
if ($result.Status -eq "Success") {
Write-Host "$server is OK"
}
else {
Write-Host "$server is NOT OK"
}
}
}