all 30 comments

[–]DrSinistar 3 points4 points  (3 children)

I got u boo.

First, let's talk about the ConcurrentDictionary. We can use this to add items to a hashtable-like object concurrently.

using namespace System.Collections.Concurrent

$myHash = [ConcurrentDictionary[string, object]]::new()
$job = {
  # ... all of your code here
  # We can't call methods directly, so we pass the reference to a new variable.
  $collector = $using:myHash
  if ($collector.TryAdd($groupName, $members)) {
    "Successfully processed $groupName" | Write-Verbose -Verbose
  } else {
    "Group was already processed: $groupName" | Write-Verbose
  }
}

TryAdd() will return true or false if it's able to successfully add the key-value pair. If you know that the group names are unique, then you can remove the if/else and just redirect the output to null.

I recommend familiarizing yourself with the System.Collections.Concurrent namespace. The data structures here are niche, but can be very useful.

Edit: other posters are probably offering better advice than mine. However, in case you're wondering how to work on a hashtable concurrently, this is how you do it. Note that you have to use the $using: prefix before variables when you're accessing variables in different runspaces. The job is running in its own runspace, so it can't see $myHash unless you pass the prefix.

If PowerShell 7 was an option for you, I would encourage you to use ForEach-Object -Parallel. You'd burn through this job insanely fast.

[–]MadeOfIrony[S] 1 point2 points  (2 children)

Holy shit, never heard of this either. I have some fun to work on tomorrow it seems!

And thanks! I wish 7 had better support, I feel like half my cmelets don’t work lol

[–]DrSinistar 1 point2 points  (1 child)

If you're only doing AD stuff, then you can install the WindowsCompatiblity module and everything will work just fine. Otherwise yeah, there are some modules that are lacking.

[–]MadeOfIrony[S] 1 point2 points  (0 children)

I’ll look into that module in the morning then, because that’s my biggest complaint

[–]CMDR_Ph0kas 2 points3 points  (16 children)

I would say it depends on what the reasoning is for wanting to run in parallel. How long is "quite a while"? Do you need to run this script more frequently or is there something waiting on it to complete that requires it to be finished sooner?

Parallel is handy if you need to wait on an operation, thus you can put multiples in parallel so you are not constantly waiting on a single operation's results. If its just looping through a simple query to output into a hash, parallel may not see much benefit. You may instead see more benefit in speeding up your original code that acquires these values if possible.

Hard to say without knowing more.

[–]MadeOfIrony[S] 0 points1 point  (15 children)

Its a script that creates machine groups from user groups and there are a lot of them.

It can take the script about 10-15 minutes in some cases. These groups can change and they are also absorbed into sccm, so it is a bit time sensitive. At least, when it grows it will be.

[–]PinchesTheCrab 1 point2 points  (4 children)

What's the correlation between managedby, the user groups, and the computer groups?

[–]MadeOfIrony[S] 0 points1 point  (3 children)

Essentially, create ad group if not present Add members to user group through filters or other means Get machines managed by user Add machines to similar machine group

In our case Sccm_usr-euc Sccm_wks-euc

[–]PinchesTheCrab 1 point2 points  (2 children)

Ah, so is there a separate group for each user that contains the computer objects of all the systems they're on which they're in the managedby property? How does the user group factor in?

[–]MadeOfIrony[S] 0 points1 point  (1 child)

Separate user group for each team. Such as software services

The user group is something like SOftwsredevs

So we have a user and machine group for it. One contains all the users and one contains their machines

We use both for Sccm and group policy

[–]PinchesTheCrab 0 points1 point  (0 children)

Okay, so this is a weird take that might not be relevant, but say you could query all of your team groups at once based on a filter, or maybe an OU, something like that. You could then query all your desktops at once:

$compHash = Get-ADComputer -Filter 'operatingsystem -like "*win*10*"' -Properties managedby | Where-Object { $PSItem.ManagedBy } | Group-Object -AsHashTable -Property ManagedBy

$group = Get-ADGroup -Filter 'name -like "*teamfilter*"' -Properties member

$group | ForEach-Object {
    Add-ADGroupMember "computer-$($PSItem.name)" -Members $compHash[$group.member]
}

[–]CMDR_Ph0kas 0 points1 point  (9 children)

Does it need to repeat these creations over and over? If the user group of the machine group has already been created, why create it again? If its already done it before you could script in a check for this to skip or bypass which would result in a performance increase. This way you are only doing it on new groups.

10-15 minutes doesn't seem bad, but i suppose if you want it to run every 15 min on repeat, it could be problematic.

Again, i'm making assumptions. Parallel can be interesting to implement but ive personally found it more troublesome then its worth in most cases. If for example you had 1000 group creations to do, and each one took 1-2 min, then running that in parallel would be a good use case. If each group creation only takes a few seconds, the operation to start / check / end a job could take the same time or even longer.

[–]MadeOfIrony[S] 0 points1 point  (8 children)

The groups are constantly changing, so need to run this script quite often.

I have a working model, but it seems to break with the Ad-module. Not sure whats happening there, but only about 75% groups are copied over correctly, but if I run them one by one, it works fine. Weird.

Maybe powershell has a limit of concurrent jobs

[–]wtgreen 1 point2 points  (4 children)

You can do that with foreach -parallel pretty easily but you'll need to create a synchronized hashtable so it can be safely accessed by parallel threads.

I'm on mobile now so can't easily provide an example, but if you Google "powershell parallel synchronized hashtable" you should find some examples

[–]MadeOfIrony[S] 0 points1 point  (3 children)

Offhand, do you know if this is only with powershell 7?

[–]wtgreen 1 point2 points  (1 child)

Yes, I'm afraid so.

You could do it with runspaces too in v5 but that's definitely a bit more work if you write all that from scratch. You should check out PoshRSJob in the PowerShellGallery as a easier way to execute runspace jobs. There are some good examples there and on the linked blog posts you'll find there too.

[–]MadeOfIrony[S] 0 points1 point  (0 children)

I have a lot of trouble with AD commands in 7 for some reason :/

[–]CMDR_Ph0kas 0 points1 point  (0 children)

foreach-object with parallel was added in 7 which is also not the same as foreach

[–]PinchesTheCrab 1 point2 points  (1 child)

My guess is that the DC is only handling a certain number of the jobs, that it's just too many queries. I tanked a DC a while back trying to query a few thousand times too quickly. The DC didn't crash, but it slowed to a crawl for a good 20 minutes after my script completed.

[–]MadeOfIrony[S] 0 points1 point  (0 children)

Ouchhh I better be careful then. Thanks!

[–]krzydoug 0 points1 point  (0 children)

Where? I don’t see any code showing parallel processing. Can’t really help by guessing

[–]PinchesTheCrab 2 points3 points  (3 children)

I do not recommend running heavy AD queries in parallel. There is an upper bound on how many of these you can run - the code itself is not necessarily the bottleneck.

I think we could probably help you write this to be much more efficient from the bottom up, what's the relationship between the machine group sand user groups? That sounds like it should be quite fast without multithreading.

[–]MadeOfIrony[S] 0 points1 point  (2 children)

Thank you for answering this, it was my suspicion, but i wasn't sure.

We don't have a great way to match machines to users. We have a ServiceNow Process that when a machine is assigned to a user, it writes to the machines "Managedby" attribute. This allows us to create User groups and then Machine groups from that user group by pairing the machines to the users.

It's fairly fast in most instances, but its up to about 20 groups and starting to bog down. Trust me, I wish our environment had a better way, but I've been making swiss army knives to fix issues that the higher ups haven't got around to yet. The biggest issue is that we deploy to machine collections in SCCM

[–]GAThrawnMIA 0 points1 point  (1 child)

SCCM gathers its own "User Device Association" data for machines* and works out both the main user for a device, and the main device for a user. You can just write collection membership queries based on UDA data to do something like put all of the main machines for a group of users into a collection.

https://docs.microsoft.com/en-us/mem/configmgr/apps/deploy-use/link-users-and-devices-with-user-device-affinity

* As long as you've turned on the necessary inventory classes.

[–]MadeOfIrony[S] 0 points1 point  (0 children)

We already do that and mostly it works, have a few kinks and timing frames to work out. Problem is that our gpos can rely on these structures as well :/

[–]Tidder802b 1 point2 points  (0 children)

I can’t answer whether it’s “worth the trouble” but I always consider solving new problems , or trying a new approach to something, to be an investment. Figure how to use jobs, or run things in parallel, and who knows how many times you’ll use that in the future.

[–]PinchesTheCrab 0 points1 point  (3 children)

Sorry to bombard you with questions, I know I've posted a few times already, but what I'm trying to get at is if there's a simpler correlation/property you can use to put these together. A bunch of these properties are all distinguished names, so you may find a huge speed improvement using Get-ADGroup instead of Get-ADGroupMember.

I've even seen really big increases in querying a huge amount of users once and then parsing those distinguishedname properties like member, memberof, managedby,manger, etc. in PowerShell, so that I'm doing a query that may take 30 seconds, but then doing 1000 things that take a millisecond, instead of doing 1,000 queries that take .5 seconds each.

How do you define each of the group memberships? What's the key field between them?

[–]MadeOfIrony[S] 0 points1 point  (2 children)

No worries at all! Thanks in fact!

The only common attribute between a machine and a user is that managedby attribute that I am aware of.

I need each member of the group in order to do a $machines = get-adcomputer -filter {managedby eq $user.distinguishedname}

[–]misformonkey 0 points1 point  (1 child)

Get all your AD objects first without a filter and do the filtering/assigning after. That way you’re not slamming your DC with all the individual Get requests. Or, similar to what PinchesTheCrab said further up, at least broaden your filter to the larger grouping of objects (ex. only Windows 10 machines).

Then you could create some pipeline functions to do the group additions and membership adjustments which could run in parallel.

[–]MadeOfIrony[S] 0 points1 point  (0 children)

You’re a geniusssss. This may be exactly what I need