This is something that’s been bugging me for a long time – when you run the out of the box execadmsvcjobs command on a server it only ensures that pending jobs on that one server are executed – when it completes it doesn’t mean that jobs on other servers in the farm have completed. This gets real annoying when you are using a script to deploy solution because end up getting errors about pending timer jobs needing to complete.
I tried a couple of different approaches to address this problem – the first was to use WMI to execute the execadmsvcjobs command remotely on each server. Problem with this approach is that for some reason the security context kept getting to changed to “NT AUTHORITY\ANONYMOUS LOGON” even though the process showed that it was running as my executing account – never figured out what the heck was going on with that so I decided to try a different approach. The next thing I tried was to reverse engineer the out of the box command and change it to execute all jobs for each server, not just the local server. This appeared to work but upon further inspection it became clear that it wasn’t working at all – there’s definitely something going on that gets whacked out when executing this way – so I was left with trying to find another approach.
What I eventually ended up with was a simple command that leveraged what I had done while trying to recreate the out of the box execadmsvcjobs command but instead of executing the job on each server it simply blocks until the jobs have all completed. It’s not exactly what I wanted but the end result is the same – the command blocks my script until the pending jobs have finished on each server thus allowing my subsequent commands to run without error. The name of this new command is gl-execadmsvcjobs
.
1using System;
2using System.Collections.Generic;
3using System.Text;
4using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
5using Microsoft.SharePoint;
6using Microsoft.SharePoint.Administration;
7using System.Threading;
8
9namespace Lapointe.SharePoint.STSADM.Commands.TimerJob
10{
11 public class ExecAdmSvcJobs : SPOperation
12 {
13 /// <summary>
14 /// Initializes a new instance of the <see cref="ExecAdmSvcJobs"/> class.
15 /// </summary>
16 public ExecAdmSvcJobs()
17 {
18 SPParamCollection parameters = new SPParamCollection();
19 parameters.Add(new SPParam("local", "l"));
20
21 StringBuilder sb = new StringBuilder();
22 sb.Append("\r\n\r\nExecutes pending timer jobs on all servers in the farm.\r\n\r\n\r\n\r\nParameters:");
23 sb.Append("\r\n\t[-local]");
24 Init(parameters, sb.ToString());
25 }
26
27 /// <summary>
28 /// Gets the help message.
29 /// </summary>
30 /// <param name="command">The command.</param>
31 /// <returns></returns>
32 public override string GetHelpMessage(string command)
33 {
34 return HelpMessage;
35 }
36
37 /// <summary>
38 /// Executes the specified command.
39 /// </summary>
40 /// <param name="command">The command.</param>
41 /// <param name="keyValues">The key values.</param>
42 /// <param name="output">The output.</param>
43 /// <returns></returns>
44 public override int Execute(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
45 {
46 output = string.Empty;
47
48 Execute(Params["local"].UserTypedIn);
49
50 return OUTPUT_SUCCESS;
51 }
52
53 /// <summary>
54 /// Executes the timer jobs.
55 /// </summary>
56 /// <param name="local">if set to <c>true</c> [local].</param>
57 public static void Execute(bool local)
58 {
59 Execute(local, false);
60 }
61
62 /// <summary>
63 /// Executes the timer jobs.
64 /// </summary>
65 /// <param name="local">if set to <c>true</c> [local].</param>
66 /// <param name="quiet">if set to <c>true</c> [quiet].</param>
67 public static void Execute(bool local, bool quiet)
68 {
69 // First run the OOTB execadmsvcjobs on the local machine to make sure that any local jobs get executed
70 if (!quiet)
71 Console.WriteLine("\r\nExecuting jobs on {0}", SPServer.Local.Name);
72
73 Utilities.RunStsAdmOperation("-o execadmsvcjobs", quiet);
74 // If local was passed in then we're basically just using the OOTB command - I included this for testing only - it's not
75 // really helpful otherwise.
76 if (!local)
77 {
78 foreach (SPServer server in SPFarm.Local.Servers)
79 {
80 // Only look at servers with a valid role.
81 if (server.Role == SPServerRole.Invalid)
82 continue;
83
84 // Don't need to check locally as we just ran the OOTB command locally so skip the local server.
85 if (server.Id.Equals(SPServer.Local.Id))
86 continue;
87
88 bool stillExecuting;
89 if (!quiet)
90 Console.WriteLine("\r\nChecking jobs on {0}", server.Name);
91
92 do
93 {
94 stillExecuting = CheckApplicableRunningJobs(server, quiet);
95
96 // If jobs are still executing then sleep for 1 second.
97 if (stillExecuting)
98 Thread.Sleep(1000);
99 } while (stillExecuting);
100 }
101 }
102 }
103 /// <summary>
104 /// Checks for applicable running jobs.
105 /// </summary>
106 /// <param name="server">The server.</param>
107 /// <param name="quiet">if set to <c>true</c> [quiet].</param>
108 /// <returns></returns>
109 private static bool CheckApplicableRunningJobs(SPServer server, bool quiet)
110 {
111 foreach (KeyValuePair<Guid, SPService> current in GetProvisionedServices(server))
112 {
113 SPService service = current.Value;
114 SPAdministrationServiceJobDefinitionCollection definitions = new SPAdministrationServiceJobDefinitionCollection(service);
115 if (CheckApplicableRunningJobs(server, definitions, quiet))
116 return true; // We've found running jobs so no point looking any further.
117
118 SPWebService service2 = service as SPWebService;
119 if (service2 != null)
120 {
121 foreach (SPWebApplication webApplication in service2.WebApplications)
122 {
123 definitions = new SPAdministrationServiceJobDefinitionCollection(webApplication);
124 if (CheckApplicableRunningJobs(server, definitions, quiet))
125 return true;
126 }
127 }
128 }
129 return false;
130 }
131
132 /// <summary>
133 /// Checks for applicable running jobs.
134 /// </summary>
135 /// <param name="server">The server.</param>
136 /// <param name="jds">The job definitions to consider.</param>
137 /// <param name="quiet">if set to <c>true</c> [quiet].</param>
138 /// <returns></returns>
139 private static bool CheckApplicableRunningJobs(SPServer server, SPAdministrationServiceJobDefinitionCollection jds, bool quiet)
140 {
141 bool stillExecuting = false;
142
143 foreach (SPJobDefinition definition in jds)
144 {
145 if (string.IsNullOrEmpty(definition.Name))
146 continue;
147
148 bool isApplicable = false;
149 if (!definition.IsDisabled)
150 isApplicable = ((definition.Server == null) || definition.Server.Id.Equals(server.Id));
151
152 if (!isApplicable)
153 {
154 // If it's not applicable then we don't really care if it's running or not.
155 continue;
156 }
157
158 if (!quiet)
159 Console.Write("Waiting on {0}.\r\n", definition.Name);
160
161 stillExecuting = true;
162 }
163 return stillExecuting;
164 }
165
166
167 /// <summary>
168 /// Gets the provisioned services.
169 /// </summary>
170 /// <param name="server">The server.</param>
171 /// <returns></returns>
172 private static Dictionary<Guid, SPService> GetProvisionedServices(SPServer server)
173 {
174 Dictionary<Guid, SPService> dictionary = new Dictionary<Guid, SPService>(8);
175 foreach (SPServiceInstance serviceInstance in server.ServiceInstances)
176 {
177 SPService service = serviceInstance.Service;
178 if (serviceInstance.Status == SPObjectStatus.Online)
179 {
180 if (dictionary.ContainsKey(service.Id))
181 continue;
182 dictionary.Add(service.Id, service);
183 }
184 }
185 return dictionary;
186
187 }
188
189 /// <summary>
190 /// This class mimics the internal equivalent and is used because the base class is abstract.
191 /// </summary>
192 internal class SPAdministrationServiceJobDefinitionCollection : SPPersistedChildCollection<SPAdministrationServiceJobDefinition>
193 {
194 /// <summary>
195 /// Initializes a new instance of the <see cref="SPAdministrationServiceJobDefinitionCollection"/> class.
196 /// </summary>
197 /// <param name="service">The service.</param>
198 internal SPAdministrationServiceJobDefinitionCollection(SPService service) : base(service)
199 {
200 }
201
202 /// <summary>
203 /// Initializes a new instance of the <see cref="SPAdministrationServiceJobDefinitionCollection"/> class.
204 /// </summary>
205 /// <param name="webApplication">The web application.</param>
206 internal SPAdministrationServiceJobDefinitionCollection(SPWebApplication webApplication) : base(webApplication)
207 {
208 }
209 }
210
211 }
212}
The help for the command is shown below:
C:\>stsadm -help gl-execadmsvcjobs
stsadm -o gl-execadmsvcjobs
Executes pending timer jobs on all servers in the farm.
Parameters:
[-local]
The following table summarizes the command and its various parameters:
Command Name | Availability | Build Date |
---|---|---|
gl-execadmsvcjobs | WSS v3, MOSS 2007 | Released: 10/25/2008 |
Parameter Name | Short Form | Required | Description | Example Usage |
---|---|---|---|---|
local | l | No | If passed in then do not consider other servers in the farm – this basically just treats the command exactly as the out of the box execadmsvcjobs command (in fact it just calls out to that command). | -local , -l |
The following is an example of how to make sure that all pending timer jobs have run on all servers in the farm:
stsadm -o gl-execadmsvcjobs