Guest Blog – Authoring a PowerShell Agent Task in XML – By Ruben Zimmermann

It is my absolute pleasure to announce this guest blog today. My good friend Ruben has come up with a fantastic community MP that is going to make all of our lives a lot easier 🙂

I will let him talk about it himself. All yours, Ruben! –


Authoring a PowerShell Agent Task in XML

Summary:

This post describes the code behind a PowerShell Agent Task. With the help of a real-life case ‘Create Log Deletion Job’ the lines in XML and PowerShell are commented.

Introduction:

Create Log Deletion Job is a SCOM – Agent Task which offers the creation of a scheduled task that deletes log files older than N days on the monitored computer. It works on SCOM 2012 R2 and later.

Requirements:

This blog assumes that you have created already management packs before. If not, or if you feel difficulties please visit the section ‘Reading’ and go through the links.

The used software is Visual Studio 2017 (2013 and 2015 work as well) plus the Visual Studio Authoring Extension. Additionally the ‘PowerShell Tools for Visual Studio 2017’ are installed.

The Community Edition of Visual Studio technically works. – Please check the license terms in advance. – If you are working for a ‘normal company’ you will most likely require Visual Studio Professional.

Realization:

The agent task mainly consists of two components.

  1. A PowerShell Script which creates a Scheduled Task and writes another script on the target machine with the parameters specified in the SCOM console.
  2. A custom module that contains a custom write action based on PowerShell Write Action and a task targeting Windows Computer leveraging the write action.
  3. Visual Studio Authoring Extension (VSAE), a free Plugin for Visual Studio is used to bind the XML and PowerShell together and produce a Management Pack.
    The Management Pack itself can be downloaded as Visual Studio solution or as compiled version directly from GitHub.

https://github.com/Juanito99/Windows.Computer.AgentTasks.CreateLogDeletionJob

Steps to do in Visual Studio:

  • Create a new project based on Management Pack / Operations Manager R2 Management Pack.- Name it ‘Windows.Computer.AgentTasks.CreateLogDeletionjob’
  • Create a folder named ‘Health Model’ and a sub folder of it named ‘Tasks’.
  • Add an Empty Management Pack Fragment to the root and name it Project.mpx.

Project File ‘project.mpx’ Content:

<ManagementPackFragment SchemaVersion="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <LanguagePacks>




    <LanguagePack ID="ENU" IsDefault="true">

      <DisplayStrings>

        <DisplayString ElementID="Windows.Computer.AgentTasks.CreateLogDeletionJob">

          <Name>Windows Computer AgentTasks CreateLogDeletionJob</Name>

          <Description>Creates a scheduled task on the managed computer which automatically deletes old logs.</Description>

        </DisplayString>

      </DisplayStrings>

    </LanguagePack>

  </LanguagePacks>




</ManagementPackFragment>

The Powershell Script:

Create a file named CreateLogDeletionJob.ps1 in the ‘Tasks'<h3> sub folder and copy the following content inside it.

param($LogFileDirectory,$LogFileType,$DaysToKeepLogs,$ScheduledTasksFolder)

$api = New-Object -ComObject 'MOM.ScriptAPI'

$api.LogScriptEvent('CreateLogDeletionJob.ps1',4000,4,"Script runs. Parameters: LogFileDirectory $($LogFileDirectory), LogFileType: $($LogFileType) DaysToKeepLogs $($DaysToKeepLogs) and scheduled task folder $($scheduledTasksFolder)")           

Write-Verbose -Message "CreateLogDeletionJob.ps1 with these parameters: LogFileDirectory $($LogFileDirectory), LogFileType: $($LogFileType) DaysToKeepLogs $($DaysToKeepLogs) and scheduled task folder $($scheduledTasksFolder)"

$ComputerName          = $env:COMPUTERNAME

$LogFileDirectoryClean = $LogFileDirectory      -Replace('\\','-')

$LogFileDirectoryClean = $LogFileDirectoryClean -Replace(':','')

$scheduledTasksFolder  = $scheduledTasksFolder -replace([char]34,'')

$scheduledTasksFolder  = $scheduledTasksFolder -replace("`"",'')

$taskName              = "Auto-Log-Dir-Cleaner_for_$($LogFileDirectoryClean)_on_$($ComputerName)"

$taskName              = $taskName -replace '\s',''

$scriptFileName        = $taskName + '.ps1'

$scriptPath            = Join-Path -Path $scheduledTasksFolder -ChildPath $scriptFileName

if ($DaysToKeepLogs -notMatch '\d' -or $DaysToKeepLogs -gt 0) {

                $daysToKeepLogs = 7

                $msg = 'Script warning. DayToKeepLogs not defined or not matching a number. Defaulting to 7 Days.'

                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4000,2,$msg)

}

if ($scheduledTasksFolder -eq $null) {

                $scheduledTasksFolder = 'C:\ScheduledTasks'

} else {

                $msg = 'Script warning. ScheduledTasksFolder not defined. Defaulting to C:\ScheduledTasks'

                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4000,2,$msg)

                Write-Warning -Message $msg

}

if ($LogFileDirectory -match 'TheLogFileDirectory') {

                $msg =  'CreateLogDeletionJobs.ps1 - Script Error. LogFileDirectory not defined. Script ends.'

                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4000,1,$msg)

                Write-Warning -Message $msg

                Exit

}

if ($LogFileType -match '\?\?\?') {    

                $msg = 'Script Error. LogFileType not defined. Script ends.'

                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4000,1,$msg)

                Write-Warning -Message $msg

                Exit

}

Function Write-LogDirCleanScript {




                param(

                                [string]$scheduledTasksFolder,

                                [string]$LogFileDirectory,                 

                                [int]$DaysToKeepLogs,                      

                                [string]$LogFileType,

                                [string]$scriptPath

                )

               

                if (Test-Path -Path $scheduledTasksFolder) {

                                $foo = 'folder exists, no action requried'

                } else {

                                & mkdir $scheduledTasksFolder

                }

               

                if (Test-Path -Path $LogFileDirectory) {

                                $foo = 'folder exists, no action requried'

                } else {

                                $msg = "Script function (Write-LogDirCleanScript, scriptPath: $($scriptPath)) failed. LogFileDirectory not found $($LogFileDirectory)"

                                Write-Warning -Message $msg

                                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4001,1,$msg)                

                                Exit

                }




                if ($LogFileType -notMatch '\*\.[a-zA-Z0-9]{3,}') {

                                $LogFileType = '*.' + $LogFileType

                                if ($LogFileType -notMatch '\*\.[a-zA-Z0-9]{3,}') {

                                                $msg = "Script function (Write-LogDirCleanScript, scriptPath: $($scriptPath)) failed. LogFileType: $($LogFileType) seems to be not correct."

                                                Write-Warning -Message $msg

                                                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4001,1,$msg)                

                                                Exit

                                }

                }




$fileContent = @"

Get-ChildItem -Path `"${LogFileDirectory}`" -Include ${LogFileType} -ErrorAction SilentlyContinue | Where-Object { ((Get-Date) - `$_.LastWriteTime).days -gt ${DaysToKeepLogs} } | Remove-Item -Force

"@         

               

                $fileContent | Set-Content -Path $scriptPath -Force

               

                if ($error) {

                                $msg = "Script function (Write-LogDirCleanScript, scriptPath: $($scriptPath)) failed. $($error)"                       

                                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4001,1,$msg)

                                Write-Warning -Message $msg

                } else {

                                $msg = "Script: $($scriptPath) successfully created"   

                                Write-Verbose -Message $msg

                }




} #End Function Write-LogDirCleanScript







Function Invoke-ScheduledTaskCreation {




                param(

                                [string]$ComputerName,                   

                                [string]$taskName

                )                

                               

                $taskRunFile         = "C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NonInteractive -File $($scriptPath)"    

                $taskStartTimeOffset = Get-Random -Minimum 1 -Maximum 10

                $taskStartTime       = (Get-Date).AddMinutes($taskStartTimeOffset) | Get-date -Format 'HH:mm'                                                                                                                                                                                     

                $taskSchedule        = 'DAILY'             

                & SCHTASKS /Create /SC $($taskSchedule) /RU `"NT AUTHORITY\SYSTEM`" /TN $($taskName) /TR $($taskRunFile) /ST $($taskStartTime)                

                               

                if ($error) {

                                $msg = "Sript function (Invoke-ScheduledTaskCreation) Failure during task creation! $($error)"

                                $api.LogScriptEvent('CreateLogDeletionJob.ps1',4002,1,$msg)                

                                Write-Warning -Message $msg

                } else {

                                $msg = "Scheduled Tasks: $($taskName) successfully created"

                                Write-Verbose -Message $msg

                }              




} #End Function Invoke-ScheduledTaskCreation







$logDirCleanScriptParams   = @{

                'scheduledTasksFolder' = $ScheduledTasksFolder

                'LogFileDirectory'     = $LogFileDirectory       

                'daysToKeepLogs'       = $DaysToKeepLogs     

                'LogFileType'          = $LogFileType

                'scriptPath'           = $scriptPath

}




Write-LogDirCleanScript @logDirCleanScriptParams







$taskCreationParams = @{

                'ComputerName'  = $ComputerName             

                'taskName'      = $taskName

                'scriptPath'    = $scriptPath

}




Invoke-ScheduledTaskCreation @taskCreationParams

The Custom Module:

Add an Empty Management Pack Fragment in the ‘Tasks’ sub folder name it ‘AgentTasks.mpx’. Copy the content below into it.

<ManagementPackFragment SchemaVersion="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">




  <TypeDefinitions>   

    <ModuleTypes>

      <!-- Defining the Write Action Module Type. The name is user-defined and will be used in the task definition below. -->

      <WriteActionModuleType ID="Windows.Computer.AgentTasks.CreateLogDeletionJob.WriteAction" Accessibility="Internal" Batching="false">

        <!-- The items in the Configuration sections are exposed through the SCOM console -->

        <Configuration>

          <xsd:element minOccurs="1" name="LogFileDirectory" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

          <xsd:element minOccurs="1" name="LogFileType" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

          <xsd:element minOccurs="1" name="ScheduledTasksFolder" type="xsd:string" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

          <xsd:element minOccurs="1" name="DaysToKeepLogs" type="xsd:integer" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

          <xsd:element minOccurs="1" name="TimeoutSeconds" type="xsd:integer" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

        </Configuration>

        <!-- To make exposed items editable it is required to specify them in the OverrideableParameters section -->

        <OverrideableParameters>

          <OverrideableParameter ID="LogFileDirectory" Selector="$Config/LogFileDirectory$" ParameterType="string" />

          <OverrideableParameter ID="LogFileType" Selector="$Config/LogFileType$" ParameterType="string" />

          <OverrideableParameter ID="ScheduledTasksFolder" Selector="$Config/ScheduledTasksFolder$" ParameterType="string" />

          <OverrideableParameter ID="DaysToKeepLogs" Selector="$Config/DaysToKeepLogs$" ParameterType="int" />         

        </OverrideableParameters>

        <ModuleImplementation Isolation="Any">

          <Composite>

            <MemberModules>

              <!-- The ID name is user-defined, suggested to keep it similar as the type. The type signals SCOM to initiate the PowerShell engine  -->

              <WriteAction ID="PowerShellWriteAction" TypeID="Windows!Microsoft.Windows.PowerShellWriteAction">

                <!-- To keep the module readable and the script better editable it is referenced below -->

                <ScriptName>CreateLogDeletionJob.ps1</ScriptName>

                <ScriptBody>$IncludeFileContent/Health Model/Tasks/CreateLogDeletionJob.ps1$</ScriptBody>

                <!-- The parameters are the same than in the Configuration section as we want to pass them into the script from the SCOM console -->

                <Parameters>

                  <Parameter>

                    <Name>LogFileDirectory</Name>

                    <Value>$Config/LogFileDirectory$</Value>

                  </Parameter>

                  <Parameter>

                    <Name>LogFileType</Name>

                    <Value>$Config/LogFileType$</Value>

                  </Parameter>

                  <Parameter>

                    <Name>ScheduledTasksFolder</Name>

                    <Value>$Config/ScheduledTasksFolder$</Value>

                  </Parameter>

                  <Parameter>

                    <Name>DaysToKeepLogs</Name>

                    <Value>$Config/DaysToKeepLogs$</Value>

                  </Parameter>

                </Parameters>

                <TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>

              </WriteAction>

            </MemberModules>

            <Composition>

              <Node ID="PowerShellWriteAction" />

            </Composition>

          </Composite>

        </ModuleImplementation>

        <OutputType>System!System.BaseData</OutputType>

        <InputType>System!System.BaseData</InputType>

      </WriteActionModuleType>     

    </ModuleTypes>

  </TypeDefinitions>




  <Monitoring>   

    <Tasks>     

      <!-- The name for the task ID is user-defined. Target is set to 'Windows.Computer' that make this task visible when objects of type Windows.Computer are shown in the console -->

      <Task ID="Windows.Computer.AgentTasks.CreateLogDeletionJob.Task" Accessibility="Public" Enabled="true" Target="Windows!Microsoft.Windows.Computer" Timeout="120" Remotable="true">

        <Category>Custom</Category>

        <!-- The name for the write action is user-defined. The TypeID though must match to the above created one. -->

        <WriteAction ID="PowerShellWriteAction" TypeID="Windows.Computer.AgentTasks.CreateLogDeletionJob.WriteAction">

          <!-- Below the parameters are pre-filled to instruct the users how the values in the overrides are exptected-->

          <LogFileDirectory>C:\TheLogFileDirectory</LogFileDirectory>

          <LogFileType>*.???</LogFileType>

          <ScheduledTasksFolder>C:\ScheduledTasks</ScheduledTasksFolder>

          <DaysToKeepLogs>7</DaysToKeepLogs>         

          <TimeoutSeconds>60</TimeoutSeconds>

        </WriteAction>

      </Task>     

    </Tasks>

  </Monitoring>




  <LanguagePacks>

    <LanguagePack ID="ENU" IsDefault="true">

      <DisplayStrings>       

        <DisplayString ElementID="Windows.Computer.AgentTasks.CreateLogDeletionJob.Task">

          <Name>Create Log Deletion Job</Name>

        </DisplayString>     

      </DisplayStrings>     

      <KnowledgeArticles></KnowledgeArticles>

    </LanguagePack>

  </LanguagePacks>




</ManagementPackFragment>

Reading:

If you are new to management pack authoring I suggest the free training material from Brian Wren.

On Microsoft’s Virtual Academy: https://mva.microsoft.com/en-US/training-courses/system-center-2012-r2-operations-manager-management-pack-8829?l=lSKqLpx2_7404984382

On Microsoft’s Wiki: https://social.technet.microsoft.com/wiki/contents/articles/15251.system-center-management-pack-authoring-guide.aspx

Closing:

If you have questions, comments or feedback feel free to feedback in our SCOM – Gitter – Community on: https://gitter.im/SCOM-Community/Lobby


Awesome! Thanks a lot for all your contribution and for being considerate for others by publishing it for everyone, Ruben 🙂 Wishing to have many more cool guest blogs from you!

You can get to know Ruben better here:

Ruben Zimmermann (A fantastic person who has a lot of great ideas) [Interview]

Cheers!

This site uses Akismet to reduce spam. Learn how your comment data is processed.