It’s been a while since my last real SharePoint 2010 scripting post but we’re getting close to RTM so I figured I need to buckle down and play some catch up and get some long overdue posts published. So, continuing my series of posts on scripting the various services and service applications within SharePoint 2010 I decided that I would share something that I know a lot of people have been struggling with recently – scripting the SharePoint Foundation Search Service.
This one threw me for a bit of a loop because all the other services and service applications can be configured almost exclusively using PowerShell cmdlets – this one though has to be configured almost exclusively using the object model. We basically have four cmdlets available to help with the configuration and unfortunately they’re not much help at all:
Get-SPSearchService
– Returns back an object representing the actual serviceGet-SPSearchServiceInstance
– Returns an object representing a service configuration for the serviceSet-SPSearchService
– Updates a few select properties associated with the serviceSet-SPSearchServiceInstance
– Updates theProxyType
for the service
The main failing with these cmdlets is that you can’t set the services process identity, the database name and server or failover server, and you can’t trigger the provisioning of the service instances which is required for the service to be considered fully “started”. All of these things I can do through Central Admin but there’s no way to do it using any provided cmdlets – so how do we solve the problem? By getting our hands dirty and writing a boat load of code against the object model.
So let’s get started. As before we’ll use an XML file to drive the setup process:
1<Services>
2 <FoundationSearchService Enable="true"
3 AddStartAddressForNonNTZone="false"
4 MaxBackupDuration="2880"
5 PerformanceLevel="PartlyReduced"
6 DatabaseServer="SPSQL1"
7 DatabaseName="SharePoint_Search_Help"
8 FailoverDatabaseServer="">
9 <SvcAccount Name="sp2010\spsearch" />
10 <CrawlAccount Name="sp2010\spcrawl" />
11 <Servers>
12 <Server Name="sp2010svr" ProxyType="Default" />
13 </Servers>
14 </FoundationSearchService>
15</Services>
As you can see the configuration file is pretty simple. We define two accounts that we’ll use, one for the process identity of the service and the other for the crawl account. There’s a few simple attributes for the database and some miscellaneous configurations and a list of all the servers in which the service should be started on.
Okay, let’s start digging into the actual script. The first thing I do is load the XML file to a variable, $svcConfig
, which I use throughout the function:
1$config = Get-Content $settingsFile
2$svcConfig = $config.Services.FoundationSearchService
Line 1 loads the file into a System.Xml.XmlDocument
typed variable and then I grab the <FoundationSearchService />
element and set that to the $svcConfig
variable. Next I need to determine if the script should continue on this server by checking the <Servers />
element to see if there’s a match for the current machine:
1#See if we want to start the svc on the current server.
2$install = (($svcConfig.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
3if (!$install) {
4 Write-Host "Machine not specified in Servers element, service will not be started on this server."
5 return
6}
So at this point we know that we’re on a target machine so the first thing we want to do is use the Start-SPServiceInstance
to start the Foundation Search Service:
1#Start the service instance
2$svc = Get-SPServiceInstance | where {$_.TypeName -eq "SharePoint Foundation Search" -and $_.Parent.Name -eq $env:ComputerName}
3if ($svc -eq $null) {
4 $svc = Get-SPServiceInstance | where {$_.TypeName -eq "SharePoint Foundation Help Search" -and $_.Parent.Name -eq $env:ComputerName}
5}
6Start-SPServiceInstance -Identity $svc
The trick with this is that if we’re not using SharePoint Foundation then once the service is initially started it renames itself to “SharePoint Foundation Help Search” so I had to put a provision to look for one name or the other to allow this script to be run multiple times and from multiple machines. Now that the service is started lets set a few variables that we’ll use throughout the rest of the script:
1#Get the service and service instance
2$searchSvc = Get-SPSearchService
3$searchSvcInstance = Get-SPSearchServiceInstance -Local
4
5$dbServer = $svcConfig.DatabaseServer
6$failoverDbServer = $svcConfig.FailoverDatabaseServer
We’ll use the $searchSvc
and $searchSvcInstance
variables extensively. Note that we’ll also need to repeat lines one and two at least a couple of times to avoid update conflicts as a result of timer jobs modifying those objects.
The next step will be to set the process identity for the service. We’ll go ahead and also get the crawl account information while we’re at it to avoid prompting for passwords in more than one location:
1#Get the service account details
2Write-Host "Provide the username and password for the search crawl account..."
3$crawlAccount = Get-Credential $svcConfig.CrawlAccount.Name
4Write-Host "Provide the username and password for the search service account..."
5 $searchSvcAccount = Get-Credential $svcConfig.SvcAccount.Name
6
7#Get or Create a managed account for the search service account.
8$searchSvcManagedAccount = (Get-SPManagedAccount -Identity $svcConfig.SvcAccount.Name -ErrorVariable err -ErrorAction SilentlyContinue)
9if ($err) {
10 $searchSvcManagedAccount = New-SPManagedAccount -Credential $searchSvcAccount
11}
12
13#Set the account details if different than what is current.
14$processIdentity = $searchSvc.ProcessIdentity
15if ($processIdentity.ManagedAccount.Username -ne $searchSvcManagedAccount.Username) {
16 $processIdentity = $searchSvc.ProcessIdentity
17 $processIdentity.CurrentIdentityType = "SpecificUser"
18 $processIdentity.ManagedAccount = $searchSvcManagedAccount
19 Write-Host "Updating the service process identity..."
20 $processIdentity.Update()
21 $searchSvc.Update()
22}
This is where things start to get interesting. I use the Get-Credential
cmdlet to return back the credentials of the user to use for the service but once I have that there’s no parameter on any cmdlet that will allow me to set the credential so I have to do it using the object model. I use the $searchSvc
variable from earlier and edit the object returned by the ProcessIdentity
property (after confirming that the value needs to be changed).
Once we have the process set we can go ahead and set the other simple properties on the service – fortunately the cmdlet Set-SPSearchService
can actually help us out with this one:
1#It doesn't hurt if this runs more than once so we don't bother checking before running.
2Write-Host "Updating the search service properties..."
3$searchSvc | Set-SPSearchService `
4 -CrawlAccount $crawlAccount.Username `
5 -CrawlPassword $crawlAccount.Password `
6 -AddStartAddressForNonNTZone $svcConfig.AddStartAddressForNonNTZone `
7 -MaxBackupDuration $svcConfig.MaxBackupDuration `
8 -PerformanceLevel $svcConfig.PerformanceLevel `
9 -ErrorVariable err `
10 -ErrorAction SilentlyContinue
11if ($err) {
12 throw $err
13}
Alright, that was the easy stuff – now we have to deal with the database. The first step is to see if there’s already a database defined for the service and if it matches what we want. This is important as we want to be able to run the script more than once so we don’t want to just blindly delete and recreate the database. The first bit of code builds a connection string using the SqlConnectionStringBuilder
object (note that in PowerShell you have to use the PSBase
property to access the properties on this object) and then compares that to what is currently set. If a match is not found then the existing database is deleted and the search service updated:
1#Build the connection string to the new database.
2[System.Data.SqlClient.SqlConnectionStringBuilder]$builder1 = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
3$builder1.psbase.DataSource = $dbServer
4$builder1.psbase.InitialCatalog = $svcConfig.DatabaseName
5$builder1.psbase.IntegratedSecurity = $true
6Write-Host "Proposed database connection: {$builder1}"
7
8[Microsoft.SharePoint.Search.Administration.SPSearchDatabase]$searchDb = $searchSvcInstance.SearchDatabase
9$dbMatch = $false
10if ($searchDb -ne $null) {
11 #A database is already set - if it's the one we expect then keep it, otherwise we want to delete it.
12 [System.Data.SqlClient.SqlConnectionStringBuilder]$builder2 = New-Object System.Data.SqlClient.SqlConnectionStringBuilder($searchDb.DatabaseConnectionString)
13 Write-Host "Existing database connection: {$builder2}"
14 if ($builder2.ConnectionString.StartsWith($builder1.ConnectionString, [StringComparison]::OrdinalIgnoreCase)) {
15 $dbMatch = $true
16 }
17 if (!$dbMatch -and $searchDb.DatabaseConnectionString.Equals($builder1.ConnectionString, [StringComparison]::OrdinalIgnoreCase)) {
18 $dbMatch = $true
19 }
20 if (!$dbMatch) {
21 #The database does not match the configuration provided so delete it.
22 Write-Host "The specified database details do not match existing details. Clearing existing."
23 $searchSvcInstance.SearchDatabase = $null
24 $searchSvcInstance.Update()
25 Write-Host "Deleting {$($searchDb.DatabaseConnectionString)}..."
26 $searchDb.Delete()
27 Write-Host "Finished deleting search DB."
28 $searchDb = $null
29 } else {
30 Write-Host "Existing Database details match provided details ($($builder2))"
31 }
32}
At this point if the $searchDb
variable is null then we want to go ahead and create a new search database:
1#If we don't have a DB go ahead and create one.
2if ($searchDb -eq $null) {
3 $dbCreated = $false
4 try {
5 Write-Host "Creating new search database {$builder1}..."
6 $searchDb = [Microsoft.SharePoint.Search.Administration.SPSearchDatabase]::Create($builder1)
7 Write-Host "Provisioning new search database..."
8 $searchDb.Provision()
9 Write-Host "Provisioning search database complete."
10 $dbCreated = $true
11
12 #Re-get the service to avoid update conflicts
13 $searchSvc = Get-SPSearchService
14 $searchSvcInstance = Get-SPSearchServiceInstance -Local
15
16 Write-Host "Associating new database with search service instance..."
17 $searchSvcInstance.SearchDatabase = $searchDb
18 Write-Host "Updating search service instance..."
19 $searchSvcInstance.Update()
20
21 #Re-get the service to avoid update conflicts
22 $searchSvc = Get-SPSearchService
23 $searchSvcInstance = Get-SPSearchServiceInstance -Local
24 } catch {
25 if ($searchDb -ne $null -and $dbCreated) {
26 Write-Warning "An error occurred updating the search service instance, deleting search database..."
27 try {
28 #Clean up
29 $searchDb.Delete()
30 } catch {
31 Write-Warning "Unable to delete search database."
32 Write-Error $_
33 }
34 }
35 throw $_
36 }
37}
I first create a new SPSearchDatabase
object by calling the static Create()
method and passing in the SqlConnectionStringBuilder
object that was previously created. I then call the Provision()
method to actually create the database on the SQL server instance. Once it’s created we can associate the database with the service by setting the SearchDatabase
property on the $searchSvcInstance
variable. If an error occurs then I attempt to delete the database from SQL Server if it’s not yet associated with the service.
Now that we have our database provisioned we can go ahead and set the failover server:
1#Set the database failover server
2if (![string]::IsNullOrEmpty($failoverDbServer)) {
3 if (($searchDb.FailoverServiceInstance -eq $null) -or `
4 ![string]::Equals($searchDb.FailoverServiceInstance.NormalizedDataSource, $failoverDbServer, [StringComparison]::OrdinalIgnoreCase)) {
5 try {
6 Write-Host "Adding failover database instance..."
7 $searchSvcInstance.SearchDatabase.AddFailoverServiceInstance($failoverDbServer)
8 Write-Host "Updating search service instance..."
9 $searchSvcInstance.Update()
10 } catch {
11 Write-Warning "Unable to set failover database server. $_"
12 }
13 }
14}
Most of the logic here is just in determining whether or not to set the failover server. Basically you just call the AddFailoverServiceInstance()
method of the SearchDatabase
property (SPSearchDatabase
) and then update the service instance.
We’re almost there – we’ve set all the properties we can now we need to complete the provisioning process:
1$status = $searchSvcInstance.Status
2#Provision the service instance on the current server
3if ($status -ne [Microsoft.SharePoint.Administration.SPObjectStatus]::Online) {
4 if ([Microsoft.SharePoint.Administration.SPServer]::Local -eq $searchSvcInstance.Server) {
5 try {
6 Write-Host "Provisioning search service instance..."
7 $searchSvcInstance.Provision()
8 } catch {
9 Write-Warning "The call to SPSearchServiceInstance.Provision (server '$($searchSvcInstance.Server.Name)') failed. Setting back to previous status '$status'. $($_.Exception)"
10 if ($status -ne $searchSvcInstance.Status) {
11 try {
12 $searchSvcInstance.Status = $status
13 $searchSvcInstance.Update()
14 } catch {
15 Write-Warning "Failed to restore previous status on the SPSearchServiceInstance (server '$($searchSvcInstance.Server.Name)'). $($_.Exception)"
16 }
17 }
18 throw $_
19 }
20 }
21}
If the service instance is not currently marked as Online
(again, accounting for multiple runs) and the service instance we’re working with is for the current machine then we call the Provision()
method on the service instance. If an error occurs provisioning the service then I try to set the status back to its previous value.
Only two steps left; First we need to create a timer job to trigger the search service instance to be provisioned on the other servers in the farm:
1#Re-get the service to avoid update conflicts
2$searchSvc = Get-SPSearchService
3
4#Create the timer job to update the instances for the other servers.
5foreach ($serviceInstance in $searchSvc.Instances) {
6 if ($serviceInstance -is [Microsoft.SharePoint.Search.Administration.SPSearchServiceInstance] `
7 -and $serviceInstance -ne $searchSvcInstance `
8 -and $serviceInstance.Status -eq [Microsoft.SharePoint.Administration.SPObjectStatus]::Online) {
9 $definition = $serviceInstance.Farm.GetObject("job-service-instance-$($serviceInstance.Id.ToString())", $serviceInstance.Farm.TimerService.Id, [Microsoft.SharePoint.Administration.SPServiceInstanceJobDefinition])
10 if ($definition -ne $null) {
11 Write-Host "A provisioning job for the SPSearchServiceInstance on server '$($serviceInstance.Server.Name)' already exists; skipping."
12 } else {
13 Write-Host "Creating provisioning job for the SPSearchServiceInstance on server '$($serviceInstance.Server.Name)'..."
14 $job = New-Object Microsoft.SharePoint.Administration.SPServiceInstanceJobDefinition($serviceInstance, $true)
15 $job.Update($true)
16 }
17 }
18}
And finally, we need to set the ProxyType
for the service instances so I loop through the <Server />
elements and call the Set-SPSearchServiceInstance
cmdlet, providing the ProxyType
attribute as defined in the XML:
1#Set the proxy type for all the service instances.
2$svcConfig.Servers.Server | ForEach-Object {
3 $server = $_
4 $instance = Get-SPSearchServiceInstance | where {$_.Server.Name -eq $server.Name}
5 if ($instance -ne $null `
6 -and $server.ProxyType.ToLowerInvariant() -ne $instance.ProxyType.ToLowerInvariant) {
7 Write-Host "Setting proxy type for $($instance.Server.Name) to $($server.ProxyType)..."
8 $instance | Set-SPSearchServiceInstance -ProxyType $server.ProxyType
9 }
10}
Phew – we’re done! Let’s put it all together now – here’s the complete script:
1function Start-FoundationSearch([string]$settingsFile = "Configurations.xml") {
2 $config = Get-Content $settingsFile
3 $svcConfig = $config.Services.FoundationSearchService
4
5 #See if we want to start the svc on the current server.
6 $install = (($svcConfig.Servers.Server | where {$_.Name -eq $env:computername}) -ne $null)
7 if (!$install) {
8 Write-Host "Machine not specified in Servers element, service will not be started on this server."
9 return
10 }
11
12 #Start the service instance
13 $svc = Get-SPServiceInstance | where {$_.TypeName -eq "SharePoint Foundation Search" -and $_.Parent.Name -eq $env:ComputerName}
14 if ($svc -eq $null) {
15 $svc = Get-SPServiceInstance | where {$_.TypeName -eq "SharePoint Foundation Help Search" -and $_.Parent.Name -eq $env:ComputerName}
16 }
17 Start-SPServiceInstance -Identity $svc
18
19 #Get the service and service instance
20 $searchSvc = Get-SPSearchService
21 $searchSvcInstance = Get-SPSearchServiceInstance -Local
22
23 $dbServer = $svcConfig.DatabaseServer
24 $failoverDbServer = $svcConfig.FailoverDatabaseServer
25
26 #Get the service account details
27 Write-Host "Provide the username and password for the search crawl account..."
28 $crawlAccount = Get-Credential $svcConfig.CrawlAccount.Name
29 Write-Host "Provide the username and password for the search service account..."
30 $searchSvcAccount = Get-Credential $svcConfig.SvcAccount.Name
31
32 #Get or Create a managed account for the search service account.
33 $searchSvcManagedAccount = (Get-SPManagedAccount -Identity $svcConfig.SvcAccount.Name -ErrorVariable err -ErrorAction SilentlyContinue)
34 if ($err) {
35 $searchSvcManagedAccount = New-SPManagedAccount -Credential $searchSvcAccount
36 }
37
38 #Set the account details if different than what is current.
39 $processIdentity = $searchSvc.ProcessIdentity
40 if ($processIdentity.ManagedAccount.Username -ne $searchSvcManagedAccount.Username) {
41 $processIdentity = $searchSvc.ProcessIdentity
42 $processIdentity.CurrentIdentityType = "SpecificUser"
43 $processIdentity.ManagedAccount = $searchSvcManagedAccount
44 Write-Host "Updating the service process identity..."
45 $processIdentity.Update()
46 $searchSvc.Update()
47 }
48
49 #It doesn't hurt if this runs more than once so we don't bother checking before running.
50 Write-Host "Updating the search service properties..."
51 $searchSvc | Set-SPSearchService `
52 -CrawlAccount $crawlAccount.Username `
53 -CrawlPassword $crawlAccount.Password `
54 -AddStartAddressForNonNTZone $svcConfig.AddStartAddressForNonNTZone `
55 -MaxBackupDuration $svcConfig.MaxBackupDuration `
56 -PerformanceLevel $svcConfig.PerformanceLevel `
57 -ErrorVariable err `
58 -ErrorAction SilentlyContinue
59 if ($err) {
60 throw $err
61 }
62
63 #Build the connection string to the new database.
64 [System.Data.SqlClient.SqlConnectionStringBuilder]$builder1 = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
65 $builder1.psbase.DataSource = $dbServer
66 $builder1.psbase.InitialCatalog = $svcConfig.DatabaseName
67 $builder1.psbase.IntegratedSecurity = $true
68 Write-Host "Proposed database connection: {$builder1}"
69
70 [Microsoft.SharePoint.Search.Administration.SPSearchDatabase]$searchDb = $searchSvcInstance.SearchDatabase
71 $dbMatch = $false
72 if ($searchDb -ne $null) {
73 #A database is already set - if it's the one we expect then keep it, otherwise we want to delete it.
74 [System.Data.SqlClient.SqlConnectionStringBuilder]$builder2 = New-Object System.Data.SqlClient.SqlConnectionStringBuilder($searchDb.DatabaseConnectionString)
75 Write-Host "Existing database connection: {$builder2}"
76 if ($builder2.ConnectionString.StartsWith($builder1.ConnectionString, [StringComparison]::OrdinalIgnoreCase)) {
77 $dbMatch = $true
78 }
79 if (!$dbMatch -and $searchDb.DatabaseConnectionString.Equals($builder1.ConnectionString, [StringComparison]::OrdinalIgnoreCase)) {
80 $dbMatch = $true
81 }
82 if (!$dbMatch) {
83 #The database does not match the configuration provided so delete it.
84 Write-Host "The specified database details do not match existing details. Clearing existing."
85 $searchSvcInstance.SearchDatabase = $null
86 $searchSvcInstance.Update()
87 Write-Host "Deleting {$($searchDb.DatabaseConnectionString)}..."
88 $searchDb.Delete()
89 Write-Host "Finished deleting search DB."
90 $searchDb = $null
91 } else {
92 Write-Host "Existing Database details match provided details ($($builder2))"
93 }
94 }
95
96 #If we don't have a DB go ahead and create one.
97 if ($searchDb -eq $null) {
98 $dbCreated = $false
99 try
100 {
101 Write-Host "Creating new search database {$builder1}..."
102 $searchDb = [Microsoft.SharePoint.Search.Administration.SPSearchDatabase]::Create($builder1)
103 Write-Host "Provisioning new search database..."
104 $searchDb.Provision()
105 Write-Host "Provisioning search database complete."
106 $dbCreated = $true
107
108 #Re-get the service to avoid update conflicts
109 $searchSvc = Get-SPSearchService
110 $searchSvcInstance = Get-SPSearchServiceInstance -Local
111
112 Write-Host "Associating new database with search service instance..."
113 $searchSvcInstance.SearchDatabase = $searchDb
114 Write-Host "Updating search service instance..."
115 $searchSvcInstance.Update()
116
117 #Re-get the service to avoid update conflicts
118 $searchSvc = Get-SPSearchService
119 $searchSvcInstance = Get-SPSearchServiceInstance -Local
120 }
121 catch
122 {
123 if ($searchDb -ne $null -and $dbCreated) {
124 Write-Warning "An error occurred updating the search service instance, deleting search database..."
125 try
126 {
127 #Clean up
128 $searchDb.Delete()
129 }
130 catch
131 {
132 Write-Warning "Unable to delete search database."
133 Write-Error $_
134 }
135 }
136 throw $_
137 }
138 }
139
140 #Set the database failover server
141 if (![string]::IsNullOrEmpty($failoverDbServer)) {
142 if (($searchDb.FailoverServiceInstance -eq $null) -or `
143 ![string]::Equals($searchDb.FailoverServiceInstance.NormalizedDataSource, $failoverDbServer, [StringComparison]::OrdinalIgnoreCase))
144 {
145 try
146 {
147 Write-Host "Adding failover database instance..."
148 $searchSvcInstance.SearchDatabase.AddFailoverServiceInstance($failoverDbServer)
149 Write-Host "Updating search service instance..."
150 $searchSvcInstance.Update()
151 }
152 catch
153 {
154 Write-Warning "Unable to set failover database server. $_"
155 }
156 }
157 }
158
159 $status = $searchSvcInstance.Status
160 #Provision the service instance on the current server
161 if ($status -ne [Microsoft.SharePoint.Administration.SPObjectStatus]::Online) {
162 if ([Microsoft.SharePoint.Administration.SPServer]::Local -eq $searchSvcInstance.Server) {
163 try
164 {
165 Write-Host "Provisioning search service instance..."
166 $searchSvcInstance.Provision()
167 }
168 catch
169 {
170 Write-Warning "The call to SPSearchServiceInstance.Provision (server '$($searchSvcInstance.Server.Name)') failed. Setting back to previous status '$status'. $($_.Exception)"
171 if ($status -ne $searchSvcInstance.Status) {
172 try
173 {
174 $searchSvcInstance.Status = $status
175 $searchSvcInstance.Update()
176 }
177 catch
178 {
179 Write-Warning "Failed to restore previous status on the SPSearchServiceInstance (server '$($searchSvcInstance.Server.Name)'). $($_.Exception)"
180 }
181 }
182 throw $_
183 }
184 }
185 }
186
187 #Re-get the service to avoid update conflicts
188 $searchSvc = Get-SPSearchService
189
190 #Create the timer job to update the instances for the other servers.
191 foreach ($serviceInstance in $searchSvc.Instances) {
192 if ($serviceInstance -is [Microsoft.SharePoint.Search.Administration.SPSearchServiceInstance] `
193 -and $serviceInstance -ne $searchSvcInstance `
194 -and $serviceInstance.Status -eq [Microsoft.SharePoint.Administration.SPObjectStatus]::Online) {
195 $definition = $serviceInstance.Farm.GetObject("job-service-instance-$($serviceInstance.Id.ToString())", $serviceInstance.Farm.TimerService.Id, [Microsoft.SharePoint.Administration.SPServiceInstanceJobDefinition])
196 if ($definition -ne $null) {
197 Write-Host "A provisioning job for the SPSearchServiceInstance on server '$($serviceInstance.Server.Name)' already exists; skipping."
198 } else {
199 Write-Host "Creating provisioning job for the SPSearchServiceInstance on server '$($serviceInstance.Server.Name)'..."
200 $job = New-Object Microsoft.SharePoint.Administration.SPServiceInstanceJobDefinition($serviceInstance, $true)
201 $job.Update($true)
202 }
203 }
204 }
205
206 #Set the proxy type for all the service instances.
207 $svcConfig.Servers.Server | ForEach-Object {
208 $server = $_
209 $instance = Get-SPSearchServiceInstance | where {$_.Server.Name -eq $server.Name}
210 if ($instance -ne $null `
211 -and $server.ProxyType.ToLowerInvariant() -ne $instance.ProxyType.ToLowerInvariant) {
212 Write-Host "Setting proxy type for $($instance.Server.Name) to $($server.ProxyType)..."
213 $instance | Set-SPSearchServiceInstance -ProxyType $server.ProxyType
214 }
215 }
216}
One thing you should note is that I’m not setting the schedule for the service. This is because the timer job class that I’d need to use to set the schedule is marked internal thus making it impossible for me to set the schedule without using reflection.
As you can see we’re in a bit of a conundrum with SharePoint 2010 – scripting your installations is considered to be a best practice and you should strive to do so whenever possible but the level of complexity involved with scripting such simple things has made it prohibitively complex for the average administrator to do.
I recognized this issue the very first day I started working with SharePoint 2010 and to solve the problem I’ve been working on a product for ShareSquared called SharePoint Composer which will allow administrators, architects, and developers to visually design their SharePoint configurations and then build out the entire Farm using the model they create in the design tool. This tool will allow you to enforce your corporate standards by clearly documenting every configuration and building the farm based on those configurations in a single-click, automated way – all without having to know any PowerShell at all! Keep a watch here for more information about SharePoint Composer.
Note – I’ve not had a chance to test this in a multi-server farm so if anyone can give me some feedback about their experiences with it I’d greatly appreciate it.