Using an ARS Script in a standard Windows Scheduler Task

It’s been ages since I last blogged as I’ve been busy regression testing all of my ARS 6.9 scripts on ARS 7.0 and while doing this bringing them all in line. The scripts have been written over the last 7 years and it’s unbelievable the changes in style that I have made from my first scripts to the latest.

I wrote an audit script that would alert me if an ARS scheduled task didn’t run or had an unexpected error, an oxymoron if I ever heard one, how could an error be anything but unexpected. This script worked well and emails me if any of the scheduled task has an issue.

But what if the audit script fails?

My solution was to run the same audit script on another server using the Windows Task Scheduler and of course this script also reports on the audit scheduled task and warns me if it didn’t run.

But what if I edit the ARS scheduled task script?

Last BUT I promise, I’m getting to the point.  I didn’t want to have to remember to update the script in multiple locations whenever a change was made.  My solution was to use the following two commands in a simple script hosted on the second windows server and run by the task scheduler that downloads the script from ARS and uses Invoke-Expression to run the script locally.

$scriptText = $(Get-QADObject “GUID of the ARS Script” -IncludedProperties edsaScriptText -connection $proxy).edsaScriptText

$returnMsg = Invoke-Expression $scriptText

You can even substitute lines of code using  $scriptText.replace  but be careful, debugging is not going to be easy.

A central store for scripts you could run on ARS or any other Windows sever using the same code

There are lots of ways of centralising your scripts ( Network Share, GPO ) but if you have to run them on both ARS and externally as in this case it’s ideal.

 

Advertisements

Updating the Password in ARS Managed Domains

I came across a post explaining how to do this and realised that for some of the managed domains the password was not being changed and these accounts were normally domain admins.

As per my last post on generating random passwords and the general discussion on if we should be forcing users to change their passwords thre is no question that it’s a good idea to automate a password reset on privileged service accounts.

ARS managed domains have the option of using either the service account that runs the ARS service or you can add an override account. If you don’t have a trust in place then an override account is the only option. For the service account that runs the ARS service you could probably use a managed service account. I’ve not done this as I sometimes have to resort to actually using this account to debug some of the ARS script policies. I use a password management tool to reset the password, update the servive configuration and then reset the service. For the override accounts I use an ARS scheduled task that changes the password in the target domain and then updates the managed domain configuration.

The excerpt of code below gets the Managed Domain Objects and selects only those where there is an override account configured. The ‘edsaUseOverrideAccount’ will be set to $true. I then iterate through them and get the ‘edsaAccountName’ which holds the NTAccount Name of the override account and I reset the domain account using ARS and then update the ‘edsaAccountPassword’ attribute of the Managed Domain object.

This appears to work well and I’ve not had any issues with it. Now nobody knows the account password and it will get reset on whatever schedule you want to run this task.

I’ve not posted my function ‘Stop-ScriptRun’ this just does some clean up and sends an email ( if instructed to do so ) and then uses ‘Throw’ to stop the script running and this updated the last run message of the scheduled task so it’s easy to see what happened on the last run by just looking at the scheduled task.

$managedDomains = Get-QADObject -SearchRoot 'CN=Managed Domains,CN=Server Configuration,CN=Configuration' -Proxy -IncludedProperties edsaUseOverrideAccount,edsaAccountName  | where { $_.edsaUseOverrideAccount }
ForEach ( $ManagedDomain in $managedDomains ) {
 $pwd = Get-RandomPassword -MaxLength $pwdMAXLength -MinLength $pwdMINLength -Upper $pwdUPPER -Lower $pwdLOWER -number $pwdNUMBER -Special $pwdSPECIAL -alphaFirst:$alphaFirst -numericFirst:$numFirst  -showdebug:$debug
 try { Set-QADUser -Identity $ManagedDomain.edsaAccountName -UserPassword $pwd -Proxy -Control @{operationReason=$operationReason} -WhatIf:$whatif }
 catch {
  Stop-ScriptRun -emailParameters $emailParameters -throwMessage "Unable to set password on account $($ManagedDomain.edsaAccountName)" -stop -sendMail
 }
 try { Set-QADObject $ManagedDomain.DN -ObjectAttributes @{edsaAccountPassword=$pwd} -Control  @{operationReason=$operationReason} -Proxy -WhatIf:$whatif }
 catch {
  Stop-ScriptRun -emailParameters $emailParameters -throwMessage "Unable to update $($ManagedDomain.name) password, $($ManagedDomain.edsaAccountName)"  -stop -sendMail
 }
}

Updating ARS scheduled task parameters with LDAPFilters in

Further to my post on doing this ( https://clan8blog.wordpress.com/2017/03/16/automating-ars-scheduled-task-parameters/) I realised that I’d left out one little trick, or two in getting and updating the scheduled task parameters:

  • The task parameters always get returned as strings so you need to convert these on the fly like this

if ( $debugLevel -eq ” ) { $defaultUsed = $true ; $debugLevel = 9 } else { $debugLevel = [int]$debugLevel }

  • A task parameter isn’t multivalued so you need to store these as comma separated strings and then convert in the script

if ( $emailAlert -eq ” ) { $defaultUsed = $true ; $emailAlert = $scriptOwner } else { $emailAlert = $emailAlert.split(“,”) }

Now you have another problem if you are using my method to upload any missing parameters as you can’t upload the array you just created! The solution is to convert it back to a comma separated string of course:  ’emailAlert’ = $($emailAlert -join “,”)

  • LDAPfilters will contain ampersands so you can’t store these in the scheduled task parameters, well you can but as with the email address example above, you need to do some conversions when you upload like this:   Convert the AMPERSANDS to “&” like this  ‘ldapFilter’     = $ldapFilter.Replace(“&”,”&”) and then upload

The reverse conversion happens automatically so no need to do a conversion.

Hope this helps

Automating ARS Scheduled Task Parameters

The blog title might seem a little confusing and I suspect that not many people are going to be searching for this.  

I thought it was a clever idea and I’ve been picking away at it for some time but I only completely solved it today.  As it turns out some of my earlier posts on how to write a function to get the ARS scheduled tasks had bugs in and I hadn’t even realised it.

I have quite a lot of ARS scheduled tasks in my environment.  I could write these to be monolithic with all the variables embedded but this means if I want to reuse the script for another scheduled task I can’t and if I want to change one of the variables at a later date I have to edit the script. So two problems,

1. the variables are not visible without reading the script and
2. I need to debug the script to change the variables.

Let’s quickly cover off how to get the scheduled task parameters. If you are running the script inside ARS, i.e. its actually the scheduled task running then you can use the intrinsic $Task.DirObject like this:

$Task.DirObj.GetInfo() 
$Task.DirObj.GetInfoEx(@('edsaParameters'),0)  
#  if no parameters are specified this command will trigger the catch clause
$strParameters         = $Task.DirObj.Get('edsaParameters')

if this is being run in an editor externally then you get the task parameters like this:

$Task = Get-QADObject -Identity $script:TaskDN  `
          -IncludedProperties edsaParameters,edsaModule -Proxy
$strParameters = $Task.edsaParameters

in both case you now need to convert the $strParameters variable to an [xml] object like this:

$strParameters = '<parameters>' + $strParameters + '</parameters>'
# and then return this variable using  
Return $strParameters
# and then convert to XML
$strParameters = Get-TaskParameters 
$xmlParameters = [xml]$strParameters

Now you can set your parameters to the scheduled task parameters like this

$emailTo       = [string]$xmlParameters.parameters.emailTo 

In my script function Get-TaskParameters I wrap these commands inside a try / catch clause. The script first tries to get the intrinsic object and falls back, using the catch clause to using the second method. As long as the $script:TaskDN is valid the function will successfully return the task parameters.

Some of my script use a lot of variables which I expose as ARS scheduled task parameters.  I always add defaults into the script so I can debug the script when writing it. I also set a flag so I know if any of the parameters were defaults rather than values taken from the scheduled task itself.  

$defaultUsed = $false # flag used to detect if all parameters were defaults
if ( ($emailTo -eq $null) -or ($emailTo.Length -le  1) ) { 
 $emailTo = "lee.andrews@myDomain.com" ; $defaultUsed = $true 
} 

In theory I don’t need to specify any parameters in the scheduled task unless I subsequently want to change the script behavior by using different task parameters. However I now have to remember which parameters I can add to the task to change it’s behavior, which in a years time may not be so obvious to me. That’s probably more like a few hours before I forget 😦  

A better way would be to copy each parameter from the script to the scheduled task but this is error prone and time consuming and as ‘scripters’ aren’t we all inherently lazy? OK maybe that’s not the best way of putting that, we like to speed things up with automation!

What if the script could update it’s own parameters on the first run or any run for that matter, how cool ( to us nerds ) would that be?

This idea had originally come to me because I had a task where one of the parameters was a group name and I thought, “what if someone changes the group name”.  I then thought “if I use the GUID of the group that couldn’t change!” Adding the group GUID as a parameter would be a nightmare so the solution was, in my mind at least, to get the script on each run to read the group name and then update the GUID in the task parameters if it needed to. I Thought I’d already posted this but it seems I can’t find it so I’ll add that as my next post.

Here is a modified version of my Get-TaskParameters function. If the scheduled task has no parameters then  
$strParameters = $Task.DirObj.Get(‘edsaParameters’) triggers the catch clause and then it uses the $script:taskDN variable to use the second method to get the parameters.

The $script:taskDN variable is used when debugging but it will fail if you reorganise your scheduled tasks. My ideal is for variable to only be required if we are debugging otherwise I want the task to dynamically figure out where it is using the intrinsic object. I’m writing more robust code if it can handle renames and container moves without modification.

If when my script runs it detects that some or all of the parameters were defaults, i.e. the task does not contain the parameter then my script creates a hash table of parameters and calls ‘Set-TaskParameters’.

Here’s all the code to do this including the new function Set-TaskParameters

function Get-TaskParameters {
 try {
  $Task.DirObj.GetInfo() 
  #  if no parameters are specified this will trigger the catch clause
  # we set the $Script:taskDNDefault dynamically in case we invoke catch
  $Script:taskDN         = $($Task.DirObj.Get('distinguishedName')) 
  $Task.DirObj.GetInfoEx(@('edsaParameters'),0)  
  $strParameters         = $Task.DirObj.Get('edsaParameters')
 }
 catch {
  try {
   $Task = Get-QADObject -Identity $script:TaskDN  `
                         -IncludedProperties edsaParameters,edsaModule `
                         -Proxy 
   $strParameters        = $Task.edsaParameters
  }
  catch {
   return 'Unable to get task parameters'
  }
 }
 $strParameters = '<parameters>' + $strParameters + '</parameters>' 
 return $strParameters
}

function Set-TaskParameters {
 param (
  $htParameters,
  $taskDN,
  $connection 
 )
 $htParameterList = @()
 foreach ( $htParameter in $htParameters.GetEnumerator() ) {
  $htParameterList += ` $('<'+$htParameter.key+'>'+$htParameter.value+'</'+$htParameter.key+'>')
 }
 Set-QADObject taskDN -ObjectAttributes @{edsaParameters=$htParameterList} `
                      -Connection $connection | Out-Null 
}

#here is example calling code 
$strParameters = Get-TaskParameters 
if ( $strParameters -eq 'Unable to get task parameters' ) {
 $subject = "$hostName $scriptName Script ERROR - Unable to get PARAMETERS"
 $emailParameters.Add('body',"$hostName  Line  $($error[0].InvocationInfo.ScriptLineNumber) - FATAL ERROR: Failed to get  script parameters")
 $emailParameters.Add('Subject',$subject)
 Send-MailMessage @emailParameters -Encoding ([System.Text.Encoding]::UTF8)
 throw $subject
}

$xmlParameters   = [xml]$strParameters
$emailTo         = [string]$xmlParameters.parameters.emailTo     
$emailFrom       = [string]$xmlParameters.parameters.emailFrom     
$operationReason = [string]$xmlParameters.parameters.operationReason
#Region Set Parameter Defaults
$defaultUsed = $false 
if ( ($emailTo -eq $null) -or ($emailTo.Length -le  1) ) { 
 $emailTo = "lee.andrews@MyDOmain.com" ; $defaultUsed = $true } 
if ( ($emailFrom -eq $null ) -or ($emailFrom.Length -le  1) ) { 
 $emailFrom = $scriptOwner ; $defaultUsed = $true } 
if ( ($operationReason -eq $null) -or ($operationReason.Length -le  1) ) {   $operationReason = $script:scriptName ; $defaultUsed = $true } 
# now check if we need to update the task parameters
if ( $defaultUsed ) {
 $taskParameters = @{
  'emailTo'         = $emailTo;
  'emailFrom'       = $emailFrom;
  'operationReason' = $operationReason;
 }
 Set-TaskParameters -htParameters $taskParameters `
                    -taskDN $script:taskDN `
                    -connection $proxy  
}

 
So your first run will automatically set all the parameters to the default values.

Is that neat or what? OK, OK I know I need to get out more 🙂
 

 

How to get the Script Module Name from the ARS Scheduled Task

How to get the Script Module Name from the ARS Scheduled Task  or how to waste a lot of time.

I wanted to get the script module name to put this into the log file when my scheduled task script was run.  Quest ( I can call them quest again now ) add an attribute that links the script module to the scheduled task.  So you would think this would be easy.  Well it’s not!

Lets look at the Scheduled task object.   I can see an attribute called edsaModule but this is a number which looks like a SID or a GUID.  Looking at the script module I can see it has an object GUID and it’s the same so BINGO it is relatively easy then….. NOT.

Lets try this in debug mode in an editor ( PowerGUI for instance ) , i.e. I can;’t get the $task.DirObj so I need to use the DN of the task when at the powershell prompt.  I hard code the DN of the Scheduled task in  $TaskDN and then I can get the task using this line of code and use includedProperties and specify edsaModule

$Task = Get-QADObject -Identity $TaskDN  -Proxy -IncludedProperties edsaParameters,edsaModule

Now I have the attribute value all I need to do to get the module name is run this command

$script:scriptName = $(Get-QADObject $Task.edsaModule -proxy ).name

This fails!

On inspection the value of $Task.edsaModule is not the same as the one when I viewed it in the ARS MMC.  it’s a string of HEX numbers.  Apparently this is an OCTET String.

Google to the rescue and I come across a function to convert this to the required GUID so now I use 2 lines of code.

$taskModule = Convert-OctetStringToGuid $Task.edsaModule
$script:scriptName = $(Get-QADObject $taskModule -proxy ).name

And BINGO this works so all I need to do is convert this to code suitable to work inside ARS this is easy right?

$Task.DirObj.GetInfo()
$OctetString = $Task.DirObj.Get(“edsaModule”)

WRONG! fails horribly and as you know debugging inside ARS itself is painful!

Not deterred I added throw statements here there and everywhere ( thats a line from a Beatles song isn’t it?) and eventually I find that $OctetString is a systemByte string.

A quick Google and it seems I can just do this

$byteString = $Task.DirObj.Get(“edsaModule”)
$byteString = [System.BitConverter]::ToString($byteString)

BUT this string has dashes inbetween the number so

$OctetString = $byteString.replace(“-“,””)

Now we are cooking on gas and can convert the octet string
$taskModule = Convert-OctetStringToGuid $OctetString

and voila I have the script module name and I can put that into my log file.

$script:scriptName = $(Get-QADObject $taskModule -proxy ).name

Of course I could just have written that into a variable but that’s not the point is it.  Now I have code that will survive the elements being renamed and it only took me about 4 hours of head scratching and some nice expletives to get there – enjoy.

Here is the conversion function to save you googling this…

function Convert-OctetStringToGuid {
param (
[string]$Guid
)
if(32 -eq $guid.Length) {
[UInt32]$a = [Convert]::ToUInt32(($guid.Substring(6, 2) + $guid.Substring(4, 2) + $guid.Substring(2, 2) + $guid.Substring(0, 2)), 16)
[UInt16]$b = [Convert]::ToUInt16(($guid.Substring(10, 2) + $guid.Substring(8, 2)), 16)
[UInt16]$c = [Convert]::ToUInt16(($guid.Substring(14, 2) + $guid.Substring(12, 2)), 16)

[byte]$d = ([Convert]::ToUInt16($guid.Substring(16, 2), 16) -as [byte])
[byte]$e = ([Convert]::ToUInt16($guid.Substring(18, 2), 16) -as [byte])
[byte]$f = ([Convert]::ToUInt16($guid.Substring(20, 2), 16) -as [byte])
[byte]$g = ([Convert]::ToUInt16($guid.Substring(22, 2), 16) -as [byte])
[byte]$h = ([Convert]::ToUInt16($guid.Substring(24, 2), 16) -as [byte])
[byte]$i = ([Convert]::ToUInt16($guid.Substring(26, 2), 16) -as [byte])
[byte]$j = ([Convert]::ToUInt16($guid.Substring(28, 2), 16) -as [byte])
[byte]$k = ([Convert]::ToUInt16($guid.Substring(30, 2), 16) -as [byte])

[Guid]$g = New-Object Guid($a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k)
return $g.Guid;
}
else {
throw “$Guid : Input string is not a valid octet string GUID”
}
}

 

Getting the script name from ARS

I like to add the scriptName in any log files or emails so that it’s reasonably easy to find the script that’s generating the error message.  You can’t think of every possible thing that can go wrong in a script so you try your best to trap the errors and handle them as best you can.

In a production script I generate emails when these occur.  The problem is the script may have been running for weeks, moths even years before the error is trapped.  So it’s important that the email tells you what script failed.

You could take the easy route and just write the script name in the email by hard coding it.

$script:scriptName = “User-ManagePSAttributes”

But what if the script is renamed later on and you forget.  No problem here you just get it dynamically like this

$thisScript = $($MyInvocation.MyCommand).Name

Sadly inside ARS this doesn’t work!

I looked at the scheduled task and there was an attribute called edsaModule which was a GUID.  When I then looked at the associated script module there was the same GUID, problem solved then.  Well not quite, when I ran the script it was not getting the script name because the returned value was not a GUID.  A quick google and I found this:

https://blog.msresource.net/2013/09/05/guids-and-octet-strings-converting-between-them/

I tested it and it converted the string returned to a GUID which I could then bind to the script module and get the name.

$Task.DirObj.GetInfo()
$Task.DirObj.GetInfoEx(@(“edsaParameters”),0)
$strParameters = $Task.DirObj.Get(“edsaParameters”)
$Script:taskDNDefault      = “Scheduled Task…: `   $($Task.DirObj.Get(“distinguishedName”)). `n”
$taskModule = Convert-OctetStringToGuid $Task.edsaModule
$script:scriptName = $(Get-QADObject $taskModule -proxy ).name

If you are testing this at the command line then you  can do the following:

$Task = Get-QADObject -Identity $TaskDN  -Proxy -IncludedProperties edsaParameters,edsaModule
$strParameters        = $Task.edsaParameters
$Script:taskDNDefault = “Scheduled Task…: $taskDN.`n”
$taskModule = Convert-OctetStringToGuid `
$Task.edsaModule   $script:scriptName = $(Get-QADObject $taskModule -proxy ).name

I’ve actually posted something similar before but when I was debugging a script today instead of the GUID I was getting the byte string which was not working when I wanted to get the script name.

https://wordpress.com/stats/insights/clan8blog.wordpress.com.

Enumeration of AD or ARS objects or Don’t tell the commandlet what you are looking for!

A recent comment to one of my posts was also a question.   Interestingly I had a colleague at work ask me almost the same question the other day.  Clearly there’s a gap in the market, ripe for a blog post 🙂 .

This is something you will kick you self about because it’s obvious really once you know how.

How can I enumerate all the objects in a container?

The question was how can I enumerate all the objects in a container.  The answer is don’t tell the commandlet what you are looking for.  OK that’s obvious isn’t it?  Well it is, if you step back and look at why you’re not getting the child objects returned.

If I use Get-QADObject <DN of an OU or Container> then I’m going to get just that OU or container object returned.  The object will have a property ParentContainer and another ParentContainerDN but you won’t find any property or method that will list the child objects, if any exist.

So how do I list all the child objects? Use the searchRoot switch.

Don’t give the commandlet an absolute object reference use the searchRoot switch instead and specify the top level container.

Remember your interviewing skills…. don’t ask closed questions or you’ll get yes or no back as an answer which won’t always be useful.

 

use the command line switches to filter the result set

Use:
-type to filter the types of object returned
-searchScope to list oneLevel ( just objects in the immediate container)  or subTree ( all child objects – this is the same as Dir /s )

In the case of an ARS scheduled task it looks like the scheduledTask and scheduledTasksContainer are both the same object type, thanks Dell.

Use Get-Member to list properties of the returned objects.

If you come across this then use Get-Member to see if there are other properties that might help to filter the results.  The classname and a WHERE clause will solve that for ARS scheduled tasks and containers.

Actually I take back that last comment about the task and the container being the same type.  I used .getType() and this returned what looked like the same object type, and ARSDirectoryObject but actually  when I used Get-Member I could see the type was listed as  edsScheduledTasksContainer and edsScheduledTask  and I checked, you can use these as a values for the type switch and it works fine.

I said it was obvious didn’t I?