Script to set RunAs Profile including an update of Account Distribution for Groups containing Computers

– Terms of use and Feedback

– Rant

– Script Features

– Script Usage

Terms of use and Feedback
WARNING! I will not take any responsibility for any damage this script might cause. Please use it in your LAB environment first.
I’m running productive with it and didn’t encounter any problems, but you never know Zwinkerndes Smiley

You can send me Feedback at andreas.zuckerhut(at)gmail.com

Rant
A while back I wrote a script for setting the Default Action Accounts per script, you can find that one here.
Then I received a few requests for setting RunAs profiles, which wasn’t a big problem. However, modifying the distribution list of more secure accounts was.
image

And if you can’t script it you gotta use your hands. And that’s like a baby’s toy.

I couldn’t even find the method at first, had to bug Jakub Oleksy, and of course he knew the function:
ApproveCredentialForDistribution<T>(Microsoft.EnterpriseManagement.ISecuredData securedData, System.Collections.Generic.IList<T> healthServiceList)
Member of Microsoft.EnterpriseManagement.ManagementGroup

The problem is, this is a generic method, which means in Powershell you can’t just be like:
$MG = new-object Microsoft.EnterpriseManagement.ManagementGroup(“localhost”)
$MG.ApproveCredentialForDistribution($Account,$HealthServiceList)

After some googling I stumbled across a blog post by Lee Holmes:
http://www.leeholmes.com/blog/2007/06/19/invoking-generic-methods-on-non-generic-classes-in-powershell/

It was very handy for getting into execution of generic methods in powershell but still, that didn’t work here.

I tried all kinds of weird combinations and I got the hint that Stefan Stranger was working on a powershell script for the distribution list modification as well:
http://blogs.technet.com/b/stefan_stranger/archive/2011/03/17/addtodistlist-tool-adding-a-computer-to-the-distribution-list-on-a-runas-account.aspx

He created a c# tool for that matter but couldn’t get it to work in powershell either so I contacted him and checked if he’d be interested in some idea-sharing and brainstorming to beat powershell.
And, he actually was, so we wrote like over 9000 e-mails back and forth on what we tried, what the result was, and what would get us any further, etc. until yesterday when we finally figured this one out.

Now you might think: Hey, this can’t be that hard right. And in fact, it isn’t, once you know how to do it.

Anyway, a common generic function would look like this:
public void DoSomething<T>(T whateveryouwant, string whocares)
Member of Whatever (Whatever would be a class, and we call the instance $Whatever)

The whole point of this is to be able to call the function with different types, let’s say string and int.
In c# this is not a problem, in powershell you have to use reflection. Check Lee Holmes’ blog post on this one.
For our little DoSomething function we would tell it that T is a string for example.

# Get the Method
$Whatever = new-object Some.Class.Whatever
$Method = $Whatever.GetType().GetMethod(“DoSomething”)
# Now build the generic method, and tell it the type T you are going to provide when invoking it, in this case a string.
$GenericMethod = $Method.MakeGenericMethod(“”.GetType())
# Per definition, invoke takes an instance and an object array containing the parameters. Let’s build it.
[System.Object[]]$Objects = (“string – for whateveryouwant”, “string – for whocares”)
# Invoke the method. $Whatever is an instance of the class, $Objects are the parameters.
$GenericMethod.Invoke($Whatever,$Objects)

And that’s pretty much it. Now let’s go back to our Approve function.
ApproveCredentialForDistribution<T>(Microsoft.EnterpriseManagement.ISecuredData securedData, System.Collections.Generic.IList<T> healthServiceList)

Any ideas for T? It expects a generic list of type MonitoringObject or PartialMonitoringObject. And that’s where I got a bit lost. Basically because I always used the whole type, therefore List<MonitoringObject> instead of just MonitoringObject. But I could blame… nevermind.
A little illustration here, it’s really just logical, which means I was just a bit dumb that day.
image

If you haven’t got Powershell 2 installed, stop right here because it’s pointless. At least I couldn’t figure out how to create a generic list of type MonitoringObject. Powershell 2 is way more friendly here.

# Create a list of type MonitoringObject
$List = New-Object ‘System.Collections.Generic.List[Microsoft.EnterpriseManagement.Monitoring.MonitoringObject]’
# It’s supposed to be a healthservice list, there is “nothing like a healthservice” in .net, it basically means that it needs instances of Microsoft.SystemCenter.HealthService
$HealthServices = (Get-MonitoringClass –name “Microsoft.SystemCenter.HealthService” | Get-MonitoringObject)
$List.Add($HealthServices[0])
# Let’s get the account
$Account = ($MG.GetMonitoringSecureData() | where {$_.Name –eq “myaccount”})

# Time to build the generic method. We get the type of one of the monitoringobjects, which then tells the generic list in the function that it’ll receive a list of monitoringobjects.
$MG = new-object Microsoft.EnterpriseManagement.ManagementGroup(“localhost”)
$Method = $MG.GetType().GetMethod(“ApproveCredentialForDistribution”)
$GenericMethod = $Method.MakeGenericMethod($HealthServices[0].GetType())
# Build the Objectarray
[System.Object[]]$Objects = ($Account,$List)
# Invoke
$GenericMethod.Invoke($MG,$Objects)

Result:
Exception calling “Invoke” with “2” argument(s): “Object of type ‘System.Management.Automation.PSObject’ cannot be converted to type ‘Microsoft.EnterpriseManagement.ISecuredData’.”
At line:1 char:21
+ $closedmethod.invoke <<<< ($mg,$objects)
+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

What basically happens is that… Powershell is just stupid. That’s what Invoke asks for:
image

And that’s what Powershell needs in this case: ‘System.Collections.Generic.List[System.Object]’
The only reason why I figured that out was because I didn’t get the whole generic function thing right at first and powershell kept throwing exceptions that it needs the whole thing wrapped around a list.
You don’t get these errors if you build it correctly. So, not knowing what I’m doing helped earlier me solving the issue later when I knew what I was doing.

If anyone can explain why it needs a list here instead of an array, please contact me. I’d love to know.

And the functional code looks like this:

And then it worked.

Bottom line: as much as I love powershell, at times it drives me nuts.

Script Features
This script takes a group containing Windows Computers, iterates through each member and loads the associated HealthService.
It then loads the old account associated with the RunAs Profile provided, and updates it if it doesn’t match the new account provided.
During runtime it creates a list of all HealthServices in the group provided, and it then updates the Distribution list of the account. If the account is set to less secure, this won’t have any effect.
And while it runs, it spawns a magical Leprechaun that brings you beer.

IMPORTANT:
– It will not update the distribution list if you are using powershell 1, you need to run powershell 2 or it’ll skip that part.
– Whether or not the account is already set to the right account, it’ll update the distribution list.

Script Usage
The script uses commandline parameters. Additionally you can change a variable in the script if you don’t want to modify the distribution list of the account.

Takes 7 parameters:
1 – RMS Name
The name of the Root Management Server

2 – GroupDisplayName
The Display Name of the Group containing the Computers for which the RunAs Account should be set

3 – RunAsProfileName
The Display Name of the RunasProfile for which the Account needs to be set

4 – AccountName
The name of the account you want to set for this group. Name is the same as display name in the console

5 – AccountType
The Account Type. You can create an account several times with the same name but a different type. Thus you have to provide the Typename.
Here another problem with Names/DisplayNames comes along. We only know the DisplayName of the types when we check the Accounts but don’t get the Name which we need.

Run this command in the Operations Manager Shell to get the Types and Names

6 – ExecutedAsTask
true or false.
If the script is run from the Shell or it’s run as a Task, set “true”. It will write all information to the console. In case its run as a task, you’ll get this output as a task result.
Additionally, whether it’s true or false, it’ll insert an Event with ID 701 in the Operations Manager Log containing the Summary information.
You can change the Id in the Write-SummaryInfo function.

7 – Simulate
true or false.
Sets the script in simulation mode. It does everything it does in usual case, outputs data etc. but won’t commit the changes

Additional Configuration
$Script:UpdateMSDList is $true per default, you can set it to $false.
You can disable the scripts ability to modify the account distribution by setting it to false.
More secure accounts need to be enabled for each Agent, if you set it to false, you have to do this manually afterwards.

Sample Configuration
I’m running the script directly on the server, therefore localhost(1).
I have a group “Test Group”(2), a profile “Test Profile”(3) and an account “Test Account”(4). Not very original, but it should do the job.
I know that “Test Account” is a Windows account, but it could also be Simple Authentication and I want to double check.

I run the following command in Powershell:

In the output I search for my account. The type is Windows(6).
image

I’m running it from the console, therefore I want to see the output of course and set executedastask to true(6).
On first run I just want to see if it finds everything and set simulate to true(7).

Final commandline then looks like this:
.\SetRunAsProfile.ps1 “localhost” “Test Group” “Test Profile” “Test Account” “Windows” true true

And the output:
image

Looks fine, once again with Simulate disabled:
.\SetRunAsProfile.ps1 “localhost” “Test Group” “Test Profile” “Test Account” “Windows” true false
image

Looks fine, time to check if it really worked:
Profile
image
Account
image

Everything’s fine.

6 thoughts on “Script to set RunAs Profile including an update of Account Distribution for Groups containing Computers

  1. Profile photo of Daniele MuscettaDaniele Muscetta

    Gotta love the conclusion of "[…] Bottom line: as much as I love powershell, at times it drives me nuts.[…]". I had discussed this with Stefan initially, and it was me who just told him "do it in C# so you don’t go insane 🙂

  2. Profile photo of Andreas ZuckerhutAndreas Zuckerhut Post author

    🙂 yea, it took me quite a few months actually to finally reapproach this project. As long as it flows it’s all fine but once you are stuck and don’t get any further it starts becoming quite pointless. This time it flowed.
    And, you can always use that knowledge elsewhere so I guess it was worth the fight.

  3. Profile photo of ShannonMercerShannonMercer

    Hi Andreas, Thanks a heap for posting this script. I have been using it for several years to set our SQL RunAs Accounts against a custom group and it has been running flawlessly. We have recently upgraded to WinServer 2012 running Powershell V4. I have removed the check PS Version segment but the script still fails at the “Adding Health Services to Account Credentials Distribution List” Error: “Error adding HealthServices to Account’s Credential List. Error: You cannot call a method on a null valued expression. You cannot call a method on a null-valued expression.” I believe it might be a problem with:

    $ObjectList = New-Object ‘System.Collections.Generic.List[System.Object]’

    $ObjectLIst.Add($account)

    $ObjectLIst.Add($approvalList)

    I’m not sure if the .NET assemblies might have changed that handled the generic list of type monitoring object. Have you by chance found a workaround? If I attempt to run it against Powershell Version 2 (powershell -version 2) I receive the same failure.

    Any input you might have would be so much appreciated. I have been banging my head against this one for a while now.

    Shannon

Leave a Reply