{"id":1980,"date":"2015-04-13T00:00:00","date_gmt":"2015-04-13T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/automating-day-to-day-powershell-admin-tasks-jobs-and-workflow\/"},"modified":"2018-05-01T07:55:29","modified_gmt":"2018-05-01T07:55:29","slug":"automating-day-to-day-powershell-admin-tasks-jobs-and-workflow","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/automating-day-to-day-powershell-admin-tasks-jobs-and-workflow\/","title":{"rendered":"Automating Day-to-Day PowerShell Admin Tasks &#8211; Jobs and Workflow"},"content":{"rendered":"<h4>The series so far:<\/h4>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/automating-day-to-day-powershell-admin-tasks---jobs-and-workflow\/\">Automating Day-to-Day PowerShell Admin Tasks \u2013 Part 1: Jobs and Workflow<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-admin-tasks-wmi,-cim-and-pswa\/\">PowerShell Day-to-Day Admin Tasks \u2013 Part 2: WMI, CIM and PSWA<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-admin-tasks-monitoring-performance\/\">PowerShell Day-to-Day Admin Tasks \u2013 Part 3: Monitoring Performance<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-sysadmin-tasks-securing-scripts\/\">PowerShell Day-to-Day Admin Tasks \u2013 Part 4: Securing Scripts<\/a><\/li>\n<li>PowerShell Day-to-Day Admin Tasks \u2013 Part 5: Events and Monitoring<\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/powershell-day-to-day-admin-tasks-part-6-real-time-it-dashboard\/\">PowerShell Day-to-Day Admin Tasks - Part 6: Real Time IT Dashboard<\/a><\/li>\n<\/ol>\n\n<h2>Overview<\/h2>\n<p>This article demonstrates the different methods you can use to simultaneously execute administration tasks on all of the devices that you manage.<\/p>\n<p>When I identify a task that I need to perform, I ask myself two questions:<\/p>\n<ul>\n<li>How many times will I have to perform it?<\/li>\n<li>How much time will it take me to do the task manually?<\/li>\n<\/ul>\n<p>By asking, and answering, these two questions, I then know if I must script the task using PowerShell.<\/p>\n<p>We are likely to start by undertaking every task in PowerShell as a separate unit. This means that when we perform a script as a set of instructions, we must then wait for its return before we interact again with the console.<\/p>\n<p>It is simple to get around this restriction by starting up a second PowerShell console in order to perform a new instruction while the first console is still running. This works well up to a point but it isn&#8217;t the ideal way for the systems administrator to manage their work because:<\/p>\n<ul>\n<li>The values of variables cannot be transmitted between consoles<\/li>\n<li>Results cannot be easily passed from one console to another<\/li>\n<li>The separate console processes cannot be individually identified<\/li>\n<\/ul>\n<p>Let&#8217;s imagine a simple example: you get to your work station tomorrow morning and you receive an email telling you to perform an action on about fifteen servers. Are you going to open fifteen PowerShell consoles? Probably not.<\/p>\n<p>A feature that is designed to resolve these problems with multiple consoles is at your disposal in PowerShell: The jobs. Let&#8217;s look at jobs in detail.<\/p>\n<h2>The jobs<\/h2>\n<p>Jobs allow you to perform background instructions and scripts asynchronously. The following instructions (Cmdlets) apply themselves to jobs:<\/p>\n<div>\n<table class=\"MsoTableMediumShading1Accent1\">\n<tbody>\n<tr>\n<td>\n<p><b> CMDLET<\/b><\/p>\n<\/td>\n<td>\n<p><b> Description<\/b><\/p>\n<\/td>\n<td>\n<p><b> Module Name<\/b><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Get-Job<\/b><\/p>\n<\/td>\n<td>\n<p>List the jobs<\/p>\n<\/td>\n<td>\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Receive-Job<\/b><\/p>\n<\/td>\n<td>\n<p>Get results from jobs<\/p>\n<\/td>\n<td>\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Remove-Job<\/b><\/p>\n<\/td>\n<td>\n<p>Delete jobs<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Resume-Job<\/b><\/p>\n<\/td>\n<td>\n<p>Restart a job<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Start-Job (1)<\/b><\/p>\n<\/td>\n<td>\n<p>Start a job<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Stop-Job (2)<\/b><\/p>\n<\/td>\n<td>\n<p>Stop a job<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Suspend-Job<\/b><\/p>\n<\/td>\n<td>\n<p>Interrupt a job<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Wait-Job<\/b><\/p>\n<\/td>\n<td>\n<p>Wait for the end of another action<\/p>\n<\/td>\n<td valign=\"top\">\n<p>Microsoft.PowerShell.Core<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>(1)\u00a0 Cannot be used for a remote instruction. The use of <b>Invoke-Command<\/b> is required. <br \/>\n(2)\u00a0 In normal time, you should not have a need for it because a job stops automatically when its actions are completed.<\/p>\n<p>\u00a0There are several types of job. Background, Remote, Scheduled and Workflow. Let&#8217;s look at them in detail.<\/p>\n<h3>BackgroundJob<\/h3>\n<p>Let&#8217;s take, for example, the following instruction that lists the active rules of your firewall, sorting them by direction.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Show-NetfirewallRule| sort direction |\u00a0?enabled-eq\"true\" | ft-property @{label=\"Name\" ; expression={$_.displayname}}, @{label=\"Direction\" ; expression={$_.direction}}\r\n<\/pre>\n<p>I deliberately used an instruction with conditions so as to have a longer processing time.<\/p>\n<p>To start a job, I simply pass the previous instruction line to <strong>Start-Job <\/strong>in a <strong>ScriptBlock<\/strong>. Then we use <strong>Get-Job<\/strong> to follow what is going on with the job.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Start-Job -ScriptBlock{Show-NetfirewallRule| sort direction |\u00a0?enabled -eq\"true\" | ft-property @{label=\"Name\" ; expression={$_.displayname}}, @{label=\"Direction\" ; expression={$_.direction}}}\r\nGet-Job\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-figure2.PNG\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_2capture-4c1cc8e1-e138-4ea8-90e0-97005ac5210e.png\" alt=\"2172-rsz_2capture-4c1cc8e1-e138-4ea8-90e\" \/><\/a><\/p>\n<p class=\"caption\">Figure 2 -Process of a background job<\/p>\n<p>The displayed result on the console contains:<\/p>\n<ul>\n<li>A unique &#8220;ID&#8221;<\/li>\n<li>A name composed by default with the following syntax\u00a0: &#8220;Job&lt;ID&gt;&#8221;<\/li>\n<li>The type of job. In our case, it&#8217;s a BackgroundJob.<\/li>\n<li>Its position: &#8220;Running&#8221;, &#8220;Failed&#8221; or &#8220;Completed&#8221;<\/li>\n<li>whether the data is available to be displayed<\/li>\n<li>The station where the instruction was performed.<\/li>\n<li>The instruction that was completed.<\/li>\n<\/ul>\n<p>The data procurement is done with the help of <strong>Receive-Job<\/strong><strong>.<\/strong><\/p>\n<div class=\"note\">\n<p class=\"note\">Note: Add the &#8211;<strong>Keep<\/strong> to <strong>Receive-Job<\/strong> for recovering the data. This lets you keep results after verification. Otherwise, if this is not done, they will be erased but the job will remain available.<\/p>\n<\/div>\n<p>I can therefore obtain the firewall rules on my computer:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt; Receive-Job -ID 2 -Keep\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-figure3.PNG\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capture1-2d6cee35-8e3c-4d6e-bd66-4b068fbf96cf.png\" alt=\"2172-rsz_capture1-2d6cee35-8e3c-4d6e-bd6\" \/><\/a><\/p>\n<p class=\"caption\">Figure 3 &#8211; Result of the Receive-Job instruction<\/p>\n<div class=\"note\">\n<p class=\"note\">Note: Always remember to delete jobs after using them.<\/p>\n<\/div>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt; Remove-Job -ID &lt;ID&gt;\r\n<\/pre>\n<p>Up to this point, there is nothing intricate in what we&#8217;re doing, but my advice would be to think about naming your jobs (Start-Job -Name &lt;JobName&gt;) for easier visibility.<\/p>\n<h3>RemoteJob<\/h3>\n<p>As a systems administrator, we would like to automate recurring tasks to a maximum. Furthermore, we are often asked to process actions on several servers and obviously to do it the quickest way possible (&#8216;ASAP Please!&#8217;). Time is of the essence. You must therefore optimise it to make your life easier. The <strong>RemoteJob<\/strong> was created for that. Below is an example:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt; Get-ItemPropertyHKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | Select-Object DisplayName,DisplayVersion,Publisher,InstallDate| ? {$_.DisplayName-ne $null} | sort DisplayName\r\n<\/pre>\n<p>This instruction lists the software (x64) installed on the computer. The search is performed in the registry and retrieves the desired information.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capture2-64a3f69d-8a60-43d5-a9ab-42a6ae589f59.png\" alt=\"2172-rsz_capture2-64a3f69d-8a60-43d5-a9a\" \/><\/p>\n<p class=\"caption\">Figure 4 &#8211; Information on the software installed in the registry<\/p>\n<p>Please note that the settings for software (x86) installations are stored in:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\r\n<\/pre>\n<p>To do this, there are several possibilities:<\/p>\n<ul>\n<li>I) Launch a job with a connection to a remote a PC through a &#8211;<strong>Scriptblock<\/strong>. Thus the job is displayed on your workstation:\n<pre class=\"lang:ps theme:powershell-ise\">Start-Job -Scriptblock {Do something -ComputerName Server1}\r\n<\/pre>\n<\/li>\n<li>(II) Launch a background job on a remote station. The instruction situated in the <strong>Scriptblock<\/strong> works remotely, but the data are displayed on your work station. The key word &#8211;<strong>Asjob<\/strong> does the job remotely with the assistance of the parameter <strong>&#8211;<\/strong><strong>Computer<\/strong><strong>Name<\/strong>.\n<pre class=\"lang:ps theme:powershell-ise\">Invoke-Command -Scriptblock{Do something} -ComputerNameServer1 -AsJob\r\n<\/pre>\n<\/li>\n<li>III) Begin a remote session on a machine. The actions done on a remote session are identical to those that are done locally. However, everything is handled on the remote unit. (We will deal with the management of remote units in more details in the second part of this series)\n<pre class=\"lang:ps theme:powershell-output\">PS &gt;Enter-PSSession-ComputerNameServer1[Server1]: PS &gt; Start-Job -ScriptBlock{Do something}\r\n<\/pre>\n<\/li>\n<\/ul>\n<h3>PSScheduleJob<\/h3>\n<p>This is the next stage. We have now used PowerShell jobs locally and remotely. Furthermore, you are aware of scheduled tasks in Windows. Do you see where I am going? We are going to schedule PowerShell jobs. The two main advantages are:<\/p>\n<ul>\n<li>The management of planned tasks with the job cmdlets. It means that we can use for example <strong>Receive-Job<\/strong> to get results of the scheduled job.<\/li>\n<li>The automatic creation of tasks in the Windows Scheduler.<\/li>\n<\/ul>\n<p>The following example retrieves all the windows services that are configured in &#8220;automatic&#8221; mode but which have not been started. This will allows us to check, each morning, if there is a problem on a server.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt;Get-wmiobject win32_service -Filter \"startmode = 'Auto' AND state != 'running' \" | select name, startname\r\n<\/pre>\n<p>For more visibility, I&#8217;ll store the instruction in a temporary <strong>S<\/strong><strong>cript<\/strong><strong>B<\/strong><strong>lock<\/strong> variable.<\/p>\n<p>I use <strong>Register-<\/strong><strong>Schedule<\/strong><strong>d<\/strong><strong>Job<\/strong> and give a name to the job.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt;$checkService = {Get-wmiobject win32_service -Filter \"startmode = 'Auto' AND state != 'running' \" | select name, startname}\r\nRegister-ScheduledJob-ScriptBlock$checkService-Name 'Check Services'\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-figure5.PNG\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capture3-b2110941-c9fd-4edd-82c0-258a109a9f6d.png\" alt=\"2172-rsz_capture3-b2110941-c9fd-4edd-82c\" \/><\/a><\/p>\n<p class=\"caption\">Figure 5 &#8211; Usage of the instruction &#8220;Register-ScheduledJob&#8221;<\/p>\n<p>[To create a scheduled job on a remote unit, it is necessary to use <strong>Invoke-Command]<\/strong><\/p>\n<p>For the time being, no trigger is linked to this job.<\/p>\n<p>Note: The Trigger is when we want to run the <strong>Schedule<\/strong><strong>d<\/strong><strong>Job<\/strong>. We will start the job at a specific date\/time, or when a specific event occurs for example: when the computer enters in an idle state.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt; Get-ScheduledJob-Id 1 | select Jobtriggers\r\nJobTriggers-----------\r\n{0}\r\n<\/pre>\n<p>The only possible action is to do it manually. In our case, we&#8217;re going to plan it. The trigger management is done through the following cmdlet:<\/p>\n<div>\n<table class=\"MsoTableMediumShading1Accent1\">\n<tbody>\n<tr>\n<td>\n<p><b> CMDLET<\/b><\/p>\n<\/td>\n<td>\n<p><b> Description<\/b><\/p>\n<\/td>\n<td>\n<p><b> Module Name<\/b><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Add-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Add Jobtrigger<\/p>\n<\/td>\n<td>\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Disable-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Disable Jobtrigger<\/p>\n<\/td>\n<td valign=\"top\">\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Enable-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Enable Jobtrigger<\/p>\n<\/td>\n<td valign=\"top\">\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Get-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Get Jobtrigger<\/p>\n<\/td>\n<td valign=\"top\">\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>New-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Create Jobtrigger<\/p>\n<\/td>\n<td valign=\"top\">\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Remove-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Delete Jobtrigger<\/p>\n<\/td>\n<td valign=\"top\">\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p><b>Set-JobTrigger<\/b><\/p>\n<\/td>\n<td>\n<p>Set Jobtrigger<\/p>\n<\/td>\n<td valign=\"top\">\n<p>PSScheduledJob<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p class=\"caption\">Figure 6 &#8211; Chart of cmdlets ScheduledJob<\/p>\n<p>First of all, we will create a Trigger that we&#8217;ll then store in the temporary $dailyTrigger. The latter will begin its actions at 7am. I like to get all the various results as I get to work each morning so as to have all necessary information for the daily check.<\/p>\n<p>Once this trigger is created, we use the cmdlet <strong>Add-<\/strong><strong>JobTrigger<\/strong> so as to link it to our previous job<strong>. <\/strong> We note that <strong>JobTriggers<\/strong> tips over from 0 to 1.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">$dailyTrigger= New-JobTrigger-Daily -At \"07:00:00\"\r\nAdd-JobTrigger-Name \"Check Services\" -Trigger $dailyTrigger\r\nGet-ScheduledJob\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-figure7.PNG\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capture4-567e0584-2b6d-4773-98ba-3a10cb1306fb.png\" alt=\"2172-rsz_capture4-567e0584-2b6d-4773-98b\" \/><\/a><\/p>\n<p class=\"caption\">Figure 7 &#8211; Management of triggers<\/p>\n<p>Let&#8217;s process a <strong>Get-<\/strong><strong>ScheduledJob<\/strong> to ensure that it is activated with the associated parameters. If necessary, I can disable the trigger with the cmdlet <strong>Disable-<\/strong><strong>JobTrigger<\/strong><\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Get-ScheduledJob-name \"Check Services\" | Get-JobTrigger\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-figure8.PNG\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capture5-52ddcc8f-ce5e-4433-8e33-d27381ac40cc.png\" alt=\"2172-rsz_capture5-52ddcc8f-ce5e-4433-8e3\" \/><\/a><\/p>\n<p class=\"caption\">Figure 8 &#8211; List of triggers<\/p>\n<p>I now open the Windows tasks scheduler and notice that the trigger \u00a0<strong>Check<\/strong><strong> Services<\/strong>\u00a0 is created in the folder <strong>PowerShell<\/strong>.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capturee-424e4308-6962-43eb-9e2f-ba5f25882d26.png\" alt=\"2172-rsz_capturee-424e4308-6962-43eb-9e2\" \/><\/p>\n<p class=\"caption\">Figure 9 &#8211; Windows tasks scheduler<\/p>\n<p>To close off on the subject of triggers, please note that some advanced options can be set. <strong>Options<\/strong> parameter is optional. Below is a full example:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">$dailyTrigger= New-JobTrigger-Daily -At \"07:00:00\"\r\n$MyOptions= New-ScheduledJobOption-RunElevated\r\n$checkService = {Get-wmiobject win32_service -Filter \"startmode = 'Auto' AND state != 'running' \" | select name, startname}\r\nRegister-ScheduledJob-ScriptBlock$checkService-Name 'Check Services'-ScheduledJobOption$MyOptions\r\nAdd-JobTrigger-Name \"Check Services\" -Trigger $dailyTrigger\r\nGet-ScheduledJob\r\n<\/pre>\n<p>The following parameters can be used to customize your Scheduled Job:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">-StartIfOnBatteries\r\n-StopIfGoingOnBatteries\r\n-WakeToRun\r\n-StartIfNotIdle\r\n-StopIfGoingOffIdle\r\n-RestartOnIdleResume\r\n-IdleDuration\r\n-IdleTimeout\r\n-ShowInTaskScheduler\r\n-RunElevated\r\n-RunWithoutNetwork\r\n-DoNotAllowDemandStart\r\n-MultipleInstancePolicy\r\n-JobDefinition\r\n<\/pre>\n<h3>PSWorkflowJob<\/h3>\n<p>Last but not least is a workflow job. So far, we understand that jobs are here to help us complete tasks simultaneously, but not all system admin jobs are amenable to this. In reality, your system has to deal with such things as<\/p>\n<ul>\n<li>The restarting of the remote station (on which you are working).<\/li>\n<li>Processing of bulk work (the expansion of virtual stations on Hyper-V, migration of mailbox on the Exchange, &#8230;)<\/li>\n<li>Having to adjourn a task.<\/li>\n<\/ul>\n<p>To show how to get around these, I shall demonstrate PowerShell workflows. They were introduced in version three of PowerShell.<\/p>\n<p>Below is a simple workflow that tells me basic facts about my operating system:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">workflow myfirstworkflow {    Get-CimInstance -ClassName Win32_OperatingSystem | select Caption, Version}\r\nmyfirstworkflow\r\n<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-1-74b2dac3-f8ab-4317-8bf1-9e8eabdfbc29.png\" alt=\"2172-1-74b2dac3-f8ab-4317-8bf1-9e8eabdfb\" \/><\/p>\n<p class=\"caption\">Figure 10 &#8211; Utilization of Workflows<\/p>\n<p>If you know how to define PowerShell functions, then you will quickly understand how to define workflows. The structure is identical; you only need to use the keyword <strong>Workflow<\/strong> followed by a name.<\/p>\n<p>Each command contained in Workflow is totally independent from the following one. This means that a variable will not be seen by another command. There is no sharing of the state of data. Restrictive? In fact, you can do it with one condition: you place them in an <strong>InlineScript<\/strong> block. PowerShell then considers it to be a stand-alone script in a one-off session.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">\u00a0\r\n    Workflow DemoWFW {\r\n    \r\n    \u00a0\u00a0\u00a0 InlineScript {\r\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $var1 = Get-Host\r\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $version = $var1 | select version\r\n    \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $version\r\n    \u00a0\u00a0\u00a0 }\r\n    \r\n    }\r\n    \r\n    DemoWFW \r\n    <\/pre>\n<p>A PowerShell workflow\u00a0may also be processed as a background job. To do this, use parameter <strong>&#8211;<\/strong><strong>AsJob<\/strong><strong>\u00a0<\/strong>when workflow requests it.<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt; DemoWFW-AsJob-PSComputerNameServer1 \r\nId  Name PSJobTypeNameState    HasMoreDataLocation  Command \r\n--    ----     ------------------   -------    ------------------  ---------    ---------- \r\n6   Job6    PSWorkflowJobRunningTrue         Server1   DemoWFW\r\n<\/pre>\n<p>Let&#8217;s now look at the simultaneous processing of commands. Tasks that run in parallel are processed by adding the keyword<strong>-Parallel<\/strong>. In this case, I use a loop in <strong>Foreach<\/strong> to process a similar action simultaneously on each of the servers on my list<strong>.<\/strong> This processing is done totally random.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">\u00a0\r\n    Workflow Get-SrvDiskDrive {\r\n    \r\n    \u00a0$servers = \"ADM01\",\"ADM11\"\r\n    \u00a0Foreach -Parallel ($srv in $servers) { \r\n    \u00a0\u00a0\u00a0 Get-CimInstance -PSComputerName $srv -ClassName Win32_DiskDrive\r\n    \u00a0} \r\n    }\r\n    \r\n    Get-SrvDiskDrive \r\n    <\/pre>\n<p>To summarise the parallel execution of activities in a workflow, I compiled a simple diagram. It is possible to launch several commands simultaneously as well as running a series of sequential commands in parallel with the keyword <strong>Sequence<\/strong> followed by the sequential commands as a <strong>S<\/strong><strong>criptBlock<\/strong>. We will then have a workflow similar to the one below:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-article1-aa281d62-4f60-41de-9e05-d02035616021.png\" alt=\"2172-article1-aa281d62-4f60-41de-9e05-d0\" \/><\/p>\n<p class=\"caption\">Figure 11 &#8211; Simplified workflow with tasks in series and simultaneous<\/p>\n<p>To illustrate my explanation, the following example will be useful run daily: the arrival of a new employee in your business.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">\u00a0\r\n        Workflow Create-NewEmployee {\r\n        \r\n        \u00a0parallel {\r\n        \u00a0\r\n        \u00a0Create VM # Command 1\r\n        \r\n        \u00a0\u00a0\u00a0 sequence {\r\n        \u00a0\u00a0\u00a0 \r\n        \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Create AD account # Command 2a\r\n        \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Create Mailbox # Command 2b\r\n        \u00a0\u00a0 \r\n        \u00a0\u00a0\u00a0 }\r\n        \u00a0\u00a0\u00a0 \r\n        Create phone configuration\u00a0 # Command 3\r\n        \u00a0} \r\n        }\r\n        \r\n        Create-NewEmployee\u00a0 \r\n    <\/pre>\n<p>You have the option to adjourn your workflow at certain stages. If you have to process one or more commands at a specific time, then you will use <strong>Suspend-Workflow<\/strong>. To retrieve it, you will need to add its job ID to the cmdlet <strong>Resume-Job<\/strong>.<\/p>\n<p>Another interesting facility is available from the Cmdlet <strong>Checkpoint-Workflow<\/strong>. You can snapshot the current data of your workflow. These snapshots include the values of variables and any output generated. If the workflow is interrupted, and subsequently resumed, then the last saved snapshot will be used and processing continued from that point. Without checkpointing, the whole workflow would have to be restarted.<\/p>\n<p>Below is an example that backs-up at the end of the processing of each server:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">     Workflow  MyWorkflow {\r\n        \r\n        \u00a0$variable = \"Server1\",\"Server2\"\r\n        \u00a0Foreach -Parallel ($var in $variable) { \r\n        \u00a0\u00a0\u00a0 # Do something ... \r\n        \u00a0\u00a0\u00a0 Checkpoint-Workflow\r\n        \u00a0} \r\n        }\r\n        \r\n        MyWorkflow \r\n        \u00a0\r\n    <\/pre>\n<p>Note that there is a performance impact when a CheckPoint is done. Data is collected and written on disk.<\/p>\n<p>I must emphasise the difference between <strong>Suspend-Workflow<\/strong> and <strong>Checkpoint-Workflow<\/strong>. <strong>Suspend-Workflow <\/strong>will pause the current command, whereas <strong>Checkpoint-Workflow<\/strong> will only persist the state of the workflow at a point in time by creating a snapshot of the current work.<\/p>\n<p>To create a workflow in PowerShell, a XAML &#8216;declarative&#8217; workflow is generated (eXtensible Application Markup Language) that is then used by Windows Workflow Foundation to run the workflow. It is possible to see this code from the console as indicated in the following example. First we create a workflow, and then look at the XAML representation of it.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">        Workflow Check-WinFeature { \r\n         \u00a0\u00a0\u00a0 Get-WindowsFeature\r\n         \u00a0}\r\n        \r\n        Check-WinFeature \r\n    <\/pre>\n<p>To obtain this workflow in the XAML format:<\/p>\n<pre class=\"lang:ps theme:powershell-output\">PS &gt; (Get-Command Check-WinFeature).XamlDefinition\r\n<\/pre>\n<p class=\"illustration\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-figure12.PNG\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2172-rsz_capture8-e131b148-fdd4-4eb3-bf38-3639b3223a5d.png\" alt=\"2172-rsz_capture8-e131b148-fdd4-4eb3-bf3\" \/><\/a><\/p>\n<p class=\"caption\">Figure 12 &#8211; Workflow in format XAML<\/p>\n<h2>Conclusion<\/h2>\n<p>We have arrived at the end of this article dedicated to administrations tasks with PowerShell. We have looked at how the use of Jobs and Workflows can help you in your routine tasks.<\/p>\n<p>A good starting point in working out the best strategy for the implementation of PowerShell tasks in a series of steps :<\/p>\n<ul>\n<li>First step : Job<\/li>\n<li>Second step : Workflow<\/li>\n<li>And finally new complex combinations. For example: a workflow within another workflow.<\/li>\n<\/ul>\n<p>From my experience, two mains benefits of creating a PowerShell Job are:<\/p>\n<ul>\n<li>Running simple commands in background. Think about the visibility of your command line.<\/li>\n<li>Reviewing the results of your jobs whenever you want.<\/li>\n<\/ul>\n<p>It makes it easier to manage a remote background Job if you collect the results in a central location unless the data is sensitive. In that case, I maintain results on the remote computer for security purposes.<\/p>\n<p>In my opinion, Workflow is very flexible and can be easily customized to match with your corporate IT environment. Workflow comes into its own if you identify those activities which can be executed in parallel and add them into a Workflow.<\/p>\n<p>I hope that you now understand better the usage of jobs and workflow while processing your daily actions. Just remember that the more your tasks are automated, the more time will save. Simplify your life to the maximum.<\/p>\n<p>&nbsp;<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>If you are aiming to optimise the use of your time by doing as much as possible via scripting, you will soon want to run scripts in parallel to save time. PowerShell does not demand that you run jobs one after the other; It has the means to launch actions whenever you wish and to obtain the results when you want them.&hellip;<\/p>\n","protected":false},"author":158223,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[35],"tags":[4635,4871],"coauthors":[6804],"class_list":["post-1980","post","type-post","status-publish","format-standard","hentry","category-powershell","tag-powershell","tag-sysadmin"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1980","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/158223"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1980"}],"version-history":[{"count":11,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1980\/revisions"}],"predecessor-version":[{"id":78637,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1980\/revisions\/78637"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1980"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1980"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1980"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1980"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}