Skip to content

Commit d41d213

Browse files
Merge pull request #301 from RandomNoun7/tickets/master/MODULES-8610-invoke-agent-job-task
(MODULES-8610) Add start agent job task
2 parents 020d484 + bea0273 commit d41d213

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
66

77
## [Unreleased]
88

9+
## Added
10+
11+
- Bolt task to start SQL agent jobs ([MODULES-8610](https://tickets.puppetlabs.com/browse/MODULES-8610)).
12+
913
## [2.4.0] - 2019-03-12
1014

1115
### Added

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,48 @@ Use this parameter — especially when using the `fuzzy_match` parameter — to
11821182
Set this to true to change the behavior of the `job_name` parameter so that only jobs with names exactly matching one of the provided job_names is returned in the result set.
11831183
This is an optional parameter which accepts either `true` or `false`. It's default value is false.
11841184

1185+
#### sqlserver::start_sqlagent_jobs
1186+
1187+
##### parameters
1188+
1189+
* **instance_name**
1190+
1191+
The name of the instance to start a job on. The value defaults to the default instance on the machine.
1192+
Refer to named instances by either the short name of the instance or by `<COMPUTERNAME>\<INSTANCE_NAME>`.
1193+
Specifying an instance name will access only that instance.
1194+
Pass the values `.`, `MSSQLSERVER`, the node name, or leave blank refer to only the default instance.
1195+
This is an optional parameter which accepts a single string or array of strings as input.
1196+
1197+
* **job_name**
1198+
1199+
The name of a job to start. By default job names must be exact.
1200+
See the `fuzzy_match` parameter below for pattern matching job names.
1201+
This parameter is required and will accept a string or an array of strings as input.
1202+
1203+
* **fuzzy_match**
1204+
1205+
Modifies the behavior of the `job_name` parameter so that the value given is a job name pattern to match.
1206+
Searches are done using the PowerShell `-match` operator.
1207+
For example, if the string `backup` is passed to the `job_name` parameter, while `fuzzy_match` is set to true, jobs such as `Daily Backup` and `System Backup` will be matched and started.
1208+
This is an optional parameter which accepts either `true` or `false`. Its default value is false.
1209+
1210+
* **step**
1211+
1212+
The step number you would like to execute. Leave blank to execute at the first step.
1213+
Keep in mind that these job step numbers refer to a zero based array of job step.
1214+
If you want to execute a job starting at step 5 you need to pass 4 to this parameter.
1215+
This is because contrary to the SQLServer user interface in many places, the programming objects
1216+
this task leverages are zero based.
1217+
1218+
* **wait**
1219+
1220+
This parameter tells the task to wait until a set of jobs has completed and then return job data.
1221+
All matched jobs are started concurrently and the task waits in a loop for all of them to complete.
1222+
If this parameter is not set to true, then matched jobs are started and the task returns job data indicating that the job is executing.
1223+
Keep in mind that jobs that complete rapidly may complete before the task has finished executing.
1224+
This could result in returned job information indicating the job is currently idle. Please inspect the `lastRunDate` property of the job and the steps to determine if the job was executed successfully.
1225+
This is a boolean parameter that defaults to false.
1226+
11851227
### Microsoft SQL Server terms
11861228

11871229
Terminology differs somewhat between various database systems; please refer to this list of terms for clarification.

tasks/start_sql_agent_job.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"puppet_task_version": 1,
3+
"supports_noop": false,
4+
"description": "Start SQL Agent jobs on a server. You can start at a specified job step number (zero based indexes), and you can either wait on the job to complete, or return immediately.",
5+
"parameters": {
6+
"instance_name": {
7+
"description": "The instance to start a job on.",
8+
"type": "Optional[Variant[Array[String],String]]"
9+
},
10+
"job_name": {
11+
"description": "The name of the job to start.",
12+
"type": "Optional[Variant[Array[String],String]]"
13+
},
14+
"fuzzy_match": {
15+
"description": "Turn the job_name parameter into a pattern to match using the PowerShell -match operator.",
16+
"type": "Optional[Boolean]"
17+
},
18+
"step": {
19+
"description": "The zero based index number of the jop step to start from. Defaults to zero.",
20+
"type": "Optional[Integer]"
21+
},
22+
"wait": {
23+
"description": "Wait for all jobs started to complete before returning data. Defaults to false such that the task will return immediately indicating only that the job was started.",
24+
"type": "Optional[Boolean]"
25+
}
26+
}
27+
}

tasks/start_sql_agent_job.ps1

+273
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
[CmdletBinding()]
2+
param (
3+
# Instance name to return jobs from
4+
[Parameter(Mandatory = $false)]
5+
[string[]]
6+
$instance_name,
7+
8+
# Name of job or match pattern
9+
[Parameter(Mandatory = $true)]
10+
[string[]]
11+
$job_name,
12+
13+
# Use -match operator name matches.
14+
[Parameter(Mandatory = $false)]
15+
[switch]
16+
$fuzzy_match,
17+
18+
# Job step to start execution from. Zero based indexes.
19+
[Parameter(Mandatory = $false)]
20+
[int]
21+
$step = 0,
22+
23+
# Wait on the job to complete before returning results
24+
[Parameter(Mandatory = $false)]
25+
[switch]
26+
$wait
27+
)
28+
29+
function Get-SQLInstances {
30+
param(
31+
[string[]]$instance_name
32+
)
33+
34+
$instancesHolder = New-Object System.Collections.Generic.List[System.Object]
35+
$stringsToReturn = New-Object System.Collections.Generic.List[System.Object]
36+
37+
# The default instance is referred to in its service name as MSSQLSERVER. This
38+
# leads many SQLSERVER people to refer to it as such. They will also connect
39+
# to it using just a '.'. None of these are it's real name. Its real instance
40+
# name is just the machine name. A named instances real name is the machine
41+
# name a '\' and the instance name. This little foreach ensures that we are
42+
# referring to these instances by their real names so that proper filtering
43+
# can be done.
44+
45+
foreach ($name in $instance_name) {
46+
switch ($name) {
47+
{($_ -eq 'MSSQLSERVER') -or ($_ -eq '.')} { [void]$instancesHolder.add($env:COMPUTERNAME) }
48+
{$_ -eq $env:COMPUTERNAME} { [void]$instancesHolder.add($_) }
49+
{$_ -notmatch '\\'} { [void]$instancesHolder.add("$env:COMPUTERNAME\$_") }
50+
default { [void]$instancesHolder.add($name) }
51+
}
52+
}
53+
54+
if ($instancesHolder.count -eq 0) {
55+
[void]$instancesHolder.add($env:computername)
56+
}
57+
58+
$instanceStrings = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server').InstalledInstances
59+
60+
# The registry key does not return the real instance names. Again we must
61+
# normalize these names into their real names so that comparisons can be done
62+
# properly.
63+
64+
foreach ($string in $instanceStrings) {
65+
switch ($string) {
66+
'MSSQLSERVER' { $string = $env:COMPUTERNAME }
67+
Default {$string = "$env:COMPUTERNAME\$string"}
68+
}
69+
70+
foreach ($instance in $instancesHolder) {
71+
if ($instance -eq $string) {
72+
[void]$stringsToReturn.add($string)
73+
}
74+
}
75+
}
76+
77+
if ($stringsToReturn.count -gt 0) {
78+
Write-Output $stringsToReturn
79+
}
80+
else {
81+
throw "No instances were found by the name(s) $instance_name"
82+
}
83+
}
84+
85+
function Get-ServerObject {
86+
param(
87+
[string]$instance
88+
)
89+
90+
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
91+
92+
Write-Output (New-Object Microsoft.SqlServer.Management.Smo.Server -ArgumentList $instance)
93+
}
94+
95+
function Select-Job {
96+
param(
97+
[PSObject]$job,
98+
[string[]]$jobsToMatch,
99+
[switch]$fuzzy_match
100+
)
101+
102+
103+
# This function takes a single SQLServer job object and compares it against
104+
# the list of names passed into the -job_name parameter of the script to
105+
# determine if this is a job the user is interested in seeing. If it does
106+
# not pass the filter represented by that parameter the job is discarded.
107+
108+
foreach ($paramJob in $jobsToMatch) {
109+
if (!$fuzzy_match) {
110+
if ($paramJob -eq $job.name) {
111+
Write-Output $job
112+
}
113+
}
114+
else {
115+
# Match is a regex operator, and it doesn't like the '\' in domain names.
116+
if ($job.name -match [regex]::escape($paramJob)) {
117+
Write-Output $job
118+
}
119+
}
120+
}
121+
}
122+
123+
function New-CustomJobObject {
124+
param(
125+
[psobject]$job
126+
)
127+
128+
$customObject = @{
129+
name = $job.name
130+
description = $job.description
131+
enabled = $job.isEnabled
132+
ownerLoginName = $job.ownerLoginName
133+
instance = $job.parent.name
134+
lastRunDate = $job.lastRunDate
135+
lastRunOutcome = [string]$job.lastRunOutcome
136+
currentRunStatus = [string]$job.currentRunStatus
137+
currentRunStep = $job.currentRunStep
138+
startStepID = $job.startStepID
139+
currentRunRetryAttempt = $job.currentRunRetryAttempt
140+
nextRunDate = $job.nextRunDate
141+
dateCreated = $job.dateCreated
142+
dateLastModified = $job.dateLastModified
143+
emailLevel = $job.emailLevel
144+
operatorToEmail = $job.operatorToEmail
145+
operatorToNetSend = $job.operatorToNetSend
146+
operatorToPage = $job.operatorToPage
147+
category = $job.category
148+
steps = New-Object System.Collections.Generic.List[System.Object]
149+
}
150+
151+
foreach ($step in $job.jobSteps) {
152+
$step = @{
153+
name = $step.name
154+
type = [string]$step.subsystem
155+
databaseName = $step.DatabaseName
156+
lastRunDate = $step.lastRunDate
157+
lastRunDurationSeconds = $step.LastRunDuration
158+
lastRunOutcome = [string]$step.LastRunOutcome
159+
lastRunRetries = $step.lastRunRetries
160+
onFailAction = [string]$step.onFailAction
161+
onFailStep = $step.onFailStep
162+
onSuccessAction = [string]$step.onSuccessAction
163+
onSuccessStep = $step.onSuccessStep
164+
retryAttempts = $step.retryAttempts
165+
retryIntervalMinutes = $step.retryInterval
166+
}
167+
[void]$customObject.steps.add($step)
168+
}
169+
170+
Write-Output $customObject
171+
}
172+
173+
174+
$errorReturn = @{
175+
_error = @{
176+
msg = ''
177+
kind = 'puppetlabs.task/task-error'
178+
details = @{
179+
detailedInfo = ''
180+
exitcode = 1
181+
}
182+
}
183+
}
184+
185+
$return = @{jobs = New-Object System.Collections.Generic.List[System.Object]}
186+
187+
$jobs = New-Object System.Collections.Generic.List[System.Object]
188+
$finishedJobs = New-Object System.Collections.Generic.List[System.Object]
189+
190+
try {
191+
$SQLInstances = Get-SQLInstances -instance_name $instance_name
192+
}
193+
catch {
194+
$errorReturn._error.msg = 'Cannot detect SQL instance names.'
195+
$errorReturn._error.details.detailedInfo = $_
196+
return $errorReturn | ConvertTo-JSON
197+
}
198+
199+
foreach ($instance in $SQLInstances) {
200+
try {
201+
$sqlServer = Get-ServerObject -instance $instance
202+
}
203+
catch {
204+
$errorReturn._error.msg = "Cannot connect to SQL Instance: $instance"
205+
$errorReturn._error.details.detailedInfo = $_
206+
return $errorReturn | ConvertTo-JSON
207+
}
208+
209+
foreach ($currentJob in $sqlserver.jobserver.jobs) {
210+
if ($MyInvocation.BoundParameters.ContainsKey('job_name')) {
211+
if ($selectedJob = (Select-Job -job $currentJob -jobsToMatch $job_name -fuzzy_match:$fuzzy_match)) {
212+
[void]$jobs.add($selectedJob)
213+
$jobName = $selectedJob.jobsteps[$step].name
214+
if([string]::IsNullOrEmpty($jobName)){
215+
$errorReturn._error.msg = `
216+
("No job step found at index {0}. There are {1} steps in the job `"{2}`". Remember that these are zero based array indexes." `
217+
-f $step, $selectedJob.jobSteps.count, $selectedJob.name)
218+
$errorReturn._error.details.detailedInfo = $_
219+
return $errorReturn | ConvertTo-JSON
220+
}
221+
$selectedJob.start($jobName)
222+
# It takes the server a little time to spin up the job. If we don't do this here
223+
# then the -wait parameter may not work later.
224+
Start-Sleep -Milliseconds 300
225+
}
226+
}
227+
else {
228+
[void]$jobs.add($currentJob)
229+
$jobName = $selectedJob.jobsteps[$step].name
230+
if([string]::IsNullOrEmpty($jobName)){
231+
$errorReturn._error.msg = `
232+
("No job step found at index {0}. There are {1} steps in the job `"{2}`". Remember that these are zero based array indexes." `
233+
-f $step, $selectedJob.jobSteps.count, $selectedJob.name)
234+
$errorReturn._error.details.detailedInfo = $_
235+
return $errorReturn | ConvertTo-JSON
236+
}
237+
$selectedJob.start($jobName)
238+
Start-Sleep -Milliseconds 300
239+
}
240+
}
241+
}
242+
243+
do {
244+
$done = $true
245+
$finishedJobs.Clear()
246+
foreach ($job in $jobs) {
247+
$job.refresh()
248+
if ([string]$job.currentRunStatus -ne 'idle') {
249+
$done = $false
250+
}
251+
[void]$finishedJobs.add((New-CustomJobObject -job $job))
252+
}
253+
} while ($wait -and !$done)
254+
255+
$return.jobs = $finishedJobs
256+
257+
return ($return | ConvertTo-JSON -Depth 99)
258+
259+
<#
260+
.SYNOPSIS
261+
This script connects to a SQL instance running on a machine and starts agent
262+
jobs.
263+
.DESCRIPTION
264+
This script will connect to SQL instances running on a machine and start agent
265+
jobs. It allows you to start the job at a specific step. Job steps are
266+
specified by integer index numbers and indexes are zero based. You can either
267+
wait for the job to complete, or you can let the task finish and return only
268+
information indicating the the job is now running. It will always return data
269+
in JSON format.
270+
.PARAMETER instance_name
271+
The name of the instance running on a machine that you would like to connect to.
272+
Leave blank to get the default instance MSSQLSERVER.
273+
#>

0 commit comments

Comments
 (0)