Powershell - Test if user is part of any Groups - powershell

I'm having trouble getting a small PS script to work the way I want it to.
Powershell version: 5.1
Ultimate goal: I want to parse all existing local User accounts and generate a list of those that are not part of any local Groups. (This is not an AD environment, nor part of a domain. This is all just local OS accounts and Groups.) Also, I am only interested in the Groups that I have manually created - I don't care about the default System Groups. (More on how I try to achieve this, later.) And then, after I get this part working, this output (list of User names) will then be used (in the future - not shown in this code) as input to another block that will add these not-in-a-Group users to a Group.
I found an example bit of code - which worked, and resulted in the correct set of user names. But it was slow - it took >5 minutes. So I wanted something faster.
The approach that I'm working with now generally seems like it will work, and is pretty quick. I'm just having trouble getting it to restrict things down correctly. It seems I'm having trouble referencing properties of objects returned by the cmdlets. (See code a bit further down. There are various counters and write-host steps in here, too, that are purely for helping me see what's going on and debugging - they aren't required for the actual task.)
My general outline of how I am going about this:
Compile a list of all Users in all Groups
But, I only want Groups that have no Description - these are the ones that I have created on the machine. The OS default Groups all have a Description. This will help me narrow down my list of Users.
Loop over the list of all Users on the system
Compare each user to the List from Step 1
If User is on the List, then that User is IN a Group, so skip it
If User is not on the List, then save/report that Name back
[Side note - the user 'WDAGUtilityAccount' turned out to also not be in any Groups, but I don't want to change that one; so have a specific test to exclude it from the list.]
[original version of code]
# --- Part 1
$UsersList = foreach ( $aGroup in get-localgroup ) {
$groupcount++
# Only want Groups that don't have a Description
if ( $null -eq $aGroup.Description ) {
Get-LocalGroupMember $aGroup.name
}
write-host "$groupCount -- $aGroup.Name _ $aGroup.Description"
}
# Just for debugging - to see the content of Part 1
write-host "List = $UsersList"
# ---- Part 2
Get-LocalUser | ForEach-Object {
$count++
$name = $_.Name
if ( $name -eq "WDAGUtilityAccount" ) {
# nothing - skip this one
} elseif ( $UsersList.Name -inotcontains "$env:COMPUTERNAME\$_" ) {
$count2++
write-host "$Count ($Count2) : $name -- not a part of any groups"
}
}
It appears that my attempts to extract the Properties of the Group in Part 1 are failing - I am getting literal text 'Name' and 'Description', instead of the aGroup.Name aGroup.Description property values. So my output looks like "MyGroup1.Name" instead of "MyGroup1" (assuming the actual name of the group is 'MyGroup1').
I believe that I am approaching the 'Group' object correctly - when I do 'get-localgroup | get-member" it says that it is a 'LocalGroup' object, with various properties (Name and Description being two of those).
There may be many other approaches to this - I'm interested in hearing general ideas; but I would also like to know the specific issues with my current code - it's a learning exercise. :)
Thanks, J
[ Version 2 of code - after some suggestions... ]
$UsersList = foreach ( $aGroup in get-localgroup ) {
$groupcount++
#Write-Host $aGroup.Description
# Only want Groups that don't have a Description (i.e. are the Groups that we created, not the default System/OS groups)
if ( $null -eq $($aGroup.Description) ) {
$UserList += Get-LocalGroupMember $($aGroup.name)
write-host "$groupCount -- $($aGroup.Name) _ $($aGroup.Description)"
}
}
write-host "List = $UsersList"
Get-LocalUser | ForEach-Object {
$count++
$name = $_.Name
if ( $name -eq "WDAGUtilityAccount" ) {
# nothing - skip this one
} elseif ( $($UsersList.name) -inotcontains "$env:COMPUTERNAME\$_" ) {
$count2++
write-host "$Count ($Count2) : $name -- not a part of any groups"
}
}

I believe this could be reduced to:
Store all members of a any group where the group's Description is null.
Get all local users and filter where their user's Name is not equal to WDAGUtilityAccount and they are not part of the stored member's SID array.
$members = Get-LocalGroup | Where-Object { -not $_.Description } | Get-LocalGroupMember
Get-LocalUser | Where-Object {
$_.Name -ne 'WDAGUtilityAccount' -and $_.SID -notin $members.SID
} | Format-Table -AutoSize

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

Powershell AD Syntax - If Statement is duplicating a string

New scripter with hopefully an easy proofread.
I made an Powershell Active Directory script that processes people on Leave of Absence (LOA) within my organization from a daily CSV file I get.
In summary my problem segment should:
Check the $Line variable's currentStatus if that employee is "On Leave"
Disable that Active Directory account.
Grab that AD account's 'Description' field and look for the string part "LOA - " and add that ONLY if it is missing. Problem is that if that field already has "LOA - " in the description, it puts another one... and another, so on.
Example:
Description (Good): LOA - Chef
Description (Bad): LOA - LOA - Chef
Description (Please NO): LOA - LOA - LOA - Chef
Etc.
I'm fairly certain the problem is with this line here but I can't figure out how to fix it.
If ($null -ne ($newDescript | ? {$loaPhrases -notcontains $_}))
$loaPhrases = "LOA -|LOA-|LOA - |LOA- |LOA - LOA - "
ElseIf ($Line.currentStatus -eq "ON LEAVE")
{
#Add 'LOA - ' to description and disable AD
Set-ADUser $User.samAccountName -Enabled 0
'Disabled AD'
$Description = Get-ADUser $User.samAccountName -Properties Description | Select-Object -ExpandProperty Description
$newDescript = $Description.substring(0, $Description.IndexOf('-')+1)
If ($null -ne ($newDescript | ? {$loaPhrases -notcontains $_}))
{
$Description = "LOA - " + "$Description"
$Description = $Description.trim()
Set-ADUser $user.samAccountName -Description $Description
}
}
It looks like you're overcomplicating it a bit. Try the -match statement for a regex matching of your $loaPhrases.....
if ($description -notmatch $loaPhrases)
This could do with fancier editing and checks, but basically checks your regex string (separated by |, as you have) for the loa string. If it's not there, it does your addition of the "LOA - " part.
Edit: Adding option to simply look at the start of a string
As Steven pointed out in the comments, it's likely you're looking for a LOA string at the start of the description to determine whether you need to add one in. As all your LOA strings begin with "LOA", you could simply check to ensure it doesn't start with "LOA", to determine if you need to add it in.
if (!($description.startswith("LOA")))
The ! flips the boolean value returned by $description.startswith("LOA"), so if it does start with "LOA", it returns false, and if it doesn't start with "LOA", it returns true, and enters the if statement.
As an aside, earlier in, you set -Enabled 0. My recommendation, for readability, is to change that to -Enabled $false, as it indicates clearly you're disabling the account, rather than setting it to an integer (even though this works / 0 usually being "false").
Building on Guy S's Helpful Answer, and considering you are new to this I wanted to make a few extra points.
Considering you are changing more than 1 property you might consider using a different approach to setting the user object. In the case of the Active Directory cmdlets there's an instancing capability that I really like. Such an approach might look something like below:
ElseIf ($Line.currentStatus -eq "ON LEAVE")
{
# Get and store the instance in a variable:
$ADUser = Get-ADUser $User.samAccountName -Properties Description
$ADUser.Enabled = $false # Change the desired properties...
Write-Host 'Disabled AD'
If( $ADUser.Description -notmatch $loaPhrases )
{
$ADUser.Description = "LOA - " + $ADUser.Description.Trim()
}
# After changing properties run a single set command citing -Instance:
Set-ADUser -Instance $ADUser
}
Notice, I used Write-Host instead of just quoting "Disable AD". In a top-down script, it's probably not a big deal but if this code were inside a function that string would get returned. That causes variant arrays, unexpected function returns, and associated problems. Again, not necessarily a problem here, but you'll want to be aware of that going forward. In fact, it's a common confusion as people get more advanced with PowerShell.
I have seen anomalies where -Instance doesn't like null values. However, the Description attribute doesn't seem to have that problem. Splatting may be another option and is super useful in situations even slightly more complex than this... Read more about splatting here,
I also want to talk a little more about your original code and the subsequent samples.
First -contains, -in and their "not" variants don't work well on strings. For example, "string" -contains "ing" will return "False". So, conceptually the approach is a bad match for what you were trying to do. Most would use -match which is a RegEx based approach.
Note: For simple stuff -Like can use a less robust wildcard approach. In your case -match is your best bet because of the variation in the match string"
-contains and -in are great for checking arrays and other collections:
$Array = "One", "Two"
$Array -contains "one"
"one" -in $Array
Above, both -contains and -in will return "True"
Given the indication that you want to search the beginning of the string, I'd modify the RegEx to use the "^" anchor like: $loaPhrases = "^LOA -|^LOA-|^LOA - |^LOA- |^LOA - LOA - " Come to think of it a simple "^LOA" may do the trick.
Regarding Guy's answer, I'd avoid .StartsWith() or for that matter .EndsWith() only because they are case sensitive.
Lastly, I'll point out the comparison to $null is unnecessary even besides the other issues.
$Array = "One", "Two"
If( ( $Array | Where-Object{ $_ -eq "Three"} ) ){
"Something"
}
In the above example "Something" will not get echoed to the screen. If you change "Three" to "Two" it will.

My script shows outputs of multiple Get-LocalGroupMember commands as one object. How to split them?

I have to make a script in PowerShell that shows all groups that user belongs to. What's more, if the switch -ListAllMembers is on, it should also show all members of these groups. The thing is that my script shows all members of different groups as one object (array) and I want them to be divided. Get-LocalGroupMember command is in a foreach loop. What can I do?
Param(
$userToCheck,
[switch]$listAllMembers
)
function CheckIfBelongs {
foreach ($user in Get-LocalGroupMember $args[0]) {
if ($user.Name -eq ($env:USERDOMAIN + "\" + $args[1])) {
return $true
}
}
return $false
}
if (!$userToCheck) {
$userToCheck = $env:USERNAME
}
Write-Host "`nUser $userToCheck belongs to these local groups:`n"
foreach ($group in Get-LocalGroup) {
if (CheckIfBelongs $group $userToCheck) {
Write-Host $group.Name
if ($listAllMembers) {
Get-LocalGroupMember $group
}
}
}
exit 0
Write-Host writes directly to the host console. Default output (as produced by Get-LocalGroupMember) goes to the success output stream. Output order is not guaranteed across streams. Also, PowerShell default formatting suppresses additional table headers when outputting objects of the same type as a table. Because of that you see only one table, and the host output before that table.
Change Write-Host to Write-Output and the output will become what you expect.
Finally, I managed to solve the issue. I used Out-String to convert the object to a string and then to send it to the console. Now, it works perfectly.

Powershell - Checking Security Group Members against another list

A bit of context, I am trying to get a list of users from a security group, then check if any of those users do not have an assigned XenDesktop.
Please forgive me, I only started using Powershell yesterday so my formatting is off. It first grabs the users from the AD group then checks if that user has an assigned desktop, which works but I what I can't seem to get working is that if the user is found in that list, I want it to move onto the next username, instead it continues to check against every machine and then onto the final bits of code.
$checkusernames = Get-ADGroupMember "***AD security Group***" | Select SamAccountName
$desktops = get-brokerdesktop -DesktopGroupName Personal_WIN8 | Select MachineName, #{Name='AssociatedUserNames';Expression={[string]::join(“;”, ($_.AssociatedUserNames))}}
foreach ($username in $checkusernames.SamAccountName) {
foreach ($desktop in $desktops) {
If ($desktop.AssociatedUserNames -like "*$username*") {
write-host $username "is assigned to" $desktop.machinename
}
write-host $username "is not assigned to a desktop"
}
Write-host $username "is not assigned to anything"
pause
}
If you want to exit from a ForEach loop early you can do so with the Break keyword. For example:
$checkusernames = Get-ADGroupMember "***AD security Group***" | Select SamAccountName
$desktops = get-brokerdesktop -DesktopGroupName Personal_WIN8 | Select MachineName, #{Name='AssociatedUserNames';Expression={[string]::join(“;”, ($_.AssociatedUserNames))}}
foreach ($username in $checkusernames.SamAccountName) {
$machine = ''
foreach ($desktop in $desktops) {
If ($desktop.AssociatedUserNames -like "*$username*") {
$machine = $desktop.machinename
break
}
}
If ($machine) {
write-host $username "is assigned to" $desktop.machinename
} Else {
write-host $username "is not assigned to a desktop"
pause
}
}
This will stop the current cycle of the inner ForEach loop (once it has completed in it's entirety) without interrupting the ongoing cycle of the outer one.
Per the dicussion in the comments, i've also reorganised the code so that you only get a single output dependent on whether a desktop is matched to a user or not and it only pauses if it does not find a match.

Powershell While Loop not Working as Intended

So here's what I'm attempting to do:
I manually input a name, and then I want to get a list of users who work under the person whose name I input (extensionattribute9 is who the user works under). However, for each person that works under that person, I also want to run the process for them, and see if anyone works under them as well. I want this process to continue until no one works under the current user.
I've managed do to this up to 3 times without a while loop, but as I don't know how deep I would have to go to get everyone, I feel using a while loop would be better overall, especially in terms of code length.
Here is the code I currently have:
$users = Get-ADUser -Filter * -Properties extensionattribute9,Displayname,mail,title
$users | ForEach-Object {
if ($_.extensionattribute9 -like '*Lynn, *')
{
$_ | select Displayname,userprincipalname,title,extensionattribute9
$name = $_.Displayname
while ($_.extensionattribute9 -ne $null){ $users | ForEach-Object {
if ($_.extensionattribute9 -eq $name)
{
$_ | select Displayname,userprincipalname,title,extensionattribute9
$name=$_.Displayname
}
}
}
}
}
When I run the code I get a user (User A) under 'Lynn', and then a user under User A. After that, nothing. The program still continues to run, but nothing gets returned. I'm guessing it's stuck in an infinite cycle, but I don't know where better to put the while loop. Help?
It sounds like you are trying to do a recursive search with nested while/for-each loops which can end badly. You can try something like this:
Function Get-Manager {
param([Object]$User)
$ManagerName = $User.extensionattribute9
# Recursion base case
if ($ManagerName -eq $null){
return
}
# Do what you want with the user here
$User | select Displayname, userprincipalname, title, extensionattribute9
# Recursive call to find manager's manager
$Manager = Get-ADUser -Filter "Name -like $ManagerName"
Get-Manager $Manager
}
# Loop through all ADusers as you did before
$Users = Get-ADUser -Filter * -Properties extensionattribute9,Displayname,mail,title
Foreach ($User in $Users) {
Get-Manager $User
}
Please note I don't have experience using Powershell with Active Directory so the syntax may be incorrect, but shouldn't be hard to fix. Hope this helps!
I'm not familiar with Powershell, but one possible reason you're having trouble is that $_ is being used to mean two different things, depending on whether you use it inside the while loop or not. Is Powershell really smart enough to know what you mean?
More important: the code
$_ | select Displayname,userprincipalname,title,extensionattribute9
$name = $_.Displayname
appears in two places close together. This is a definite code smell. It should appear once and only once.
When you're traversing a hierarchy and you don't know how deep it will go, you must use a recursive algorithm (a function that calls itself). Here it is in pseudocode:
function myFunc ($node) {
//report the name of this node
echo "node $node found." ;
// check to see if this node has any child nodes
array $children = getChildNodes ($node) ;
if (count($children) == 0) {
//no child nodes, get out of here
return ;
}
//repeat the process for each child
foreach($children as $child) {
myFunc($child) ;
}
}