It’s been a couple of weeks since my last post – I’ve been traveling and taking some needed vacation time. But I’m back to work now and I’ve got a couple new commands that I just created today.

One nice to have for our company was to be able to specify the home directory (virtual directory path) for our web applications – we didn’t want to use the default value that gets set when you use extendvs via stsadm. I was originally just going to leave the default directory (c:\inetpub\wwwroot\wss\virtualdirectories\{hostheader}{port}) but then decided that I prefer it to be elsewhere. Problem is that if you use extendvs you cannot set the path (nor can you set the port). This seemed a bit annoying to me so I decided to do some digging. With a quick search I discovered the SPWebApplicationBuilder class (this post got me started: http://forums.microsoft.com/TechNet/ShowPost.aspx?PostID=1238244&SiteID=17).

Using this class is very simple – you just create a new instance passing in the an SPFarm object, set the properties of interest, and then call Create(). Once you’ve created the web application you need to provision it and then add it to the list of web applications in Central Admin. You can then choose to create the root site if desired. The code for all of this is very straight forward:

  1using System;
  2using System.IO;
  3using System.Reflection;
  4using System.Security;
  5using System.Text;
  6using Lapointe.SharePoint.STSADM.Commands.SPValidators;
  7using Microsoft.SharePoint;
  8using Microsoft.SharePoint.Administration;
  9using Microsoft.SharePoint.Search.Administration;
 10using Microsoft.SharePoint.Utilities;
 11using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
 12 
 13namespace Lapointe.SharePoint.STSADM.Commands.WebApplications
 14{
 15    public class CreateWebApp : SPOperation
 16    {
 17        /// <summary>
 18        /// Initializes a new instance of the <see cref="CreateWebApp"/> class.
 19        /// </summary>
 20        public CreateWebApp()
 21        {
 22            SPParamCollection parameters = new SPParamCollection();
 23            parameters.Add(new SPParam("url", "url", true, null, new SPUrlValidator()));
 24            parameters.Add(new SPParam("directory", "dir", false, null, new SPNonEmptyValidator(),
 25                                       "Please specify the virtual directory for the web application."));
 26            parameters.Add(new SPParam("port", "p", false, "80", new SPIntRangeValidator(0, int.MaxValue),
 27                                       "Please specify the port for the web application."));
 28            parameters.Add(new SPParam("timezone", "tz", false, null, new SPIntRangeValidator(0, ushort.MaxValue)));
 29            parameters.Add(new SPParam("description", "desc", false, null, new SPNullOrNonEmptyValidator()));
 30            parameters.Add(new SPParam("sethostheader", "sethh", false, null, new SPNullOrNonEmptyValidator()));
 31            parameters.Add(new SPParam("exclusivelyusentlm", "ntlm"));
 32            parameters.Add(new SPParam("allowanonymous", "anon"));
 33            parameters.Add(new SPParam("ssl", "ssl"));
 34 
 35            string normalizedDataSource;
 36            string defaultDatabaseUsername;
 37            string defaultDatabasePassword;
 38            SPFarm local = SPFarm.Local;
 39            if (local != null)
 40            {
 41                SPWebService service = local.Services.GetValue<SPWebService>();
 42                normalizedDataSource = service.DefaultDatabaseInstance.NormalizedDataSource;
 43                defaultDatabaseUsername = service.DefaultDatabaseUsername;
 44                defaultDatabasePassword = service.DefaultDatabasePassword;
 45                if ((normalizedDataSource == null) || (normalizedDataSource.Length == 0))
 46                {
 47                    normalizedDataSource = Environment.MachineName;
 48                }
 49            }
 50            else
 51            {
 52                Console.WriteLine(SPResource.GetString("NoFarmObject", new object[0]));
 53                return;
 54            }
 55            if (defaultDatabaseUsername == null)
 56            {
 57                defaultDatabaseUsername = "";
 58            }
 59            if (defaultDatabasePassword == null)
 60            {
 61                defaultDatabasePassword = "";
 62            }
 63            parameters.Add(new SPParam("databaseserver", "ds", false, normalizedDataSource, new SPNonEmptyValidator()));
 64            parameters.Add(new SPParam("databasename", "dn", false, null, new SPNullOrNonEmptyValidator()));
 65            parameters.Add(new SPParam("databaseuser", "du", false, defaultDatabaseUsername, null));
 66            parameters.Add(new SPParam("databasepassword", "dp", false, defaultDatabasePassword, null));
 67 
 68            parameters.Add(new SPParam("apidname", "apid", false, "DefaultAppPool", new SPNonEmptyValidator()));
 69            parameters.Add(new SPParam("apidtype", "apidtype", false, "NetworkService",
 70                                       new SPRegexValidator("^configurableid$|^networkservice$")));
 71            parameters.Add(new SPParam("apidlogin", "apu", true, null, new SPNonEmptyValidator()));
 72            parameters.Add(new SPParam("apidpwd", "app", true, null, new SPNonEmptyValidator()));
 73 
 74            parameters.Add(new SPParam("donotcreatesite", "nosite"));
 75            parameters.Add(new SPParam("ownerlogin", "ol", true, null, new SPNonEmptyValidator()));
 76            parameters.Add(new SPParam("ownername", "on", false, null, null));
 77            parameters.Add(new SPParam("owneremail", "oe", true, null,
 78                                       new SPRegexValidator(@"^[^ \r\t\n\f@]+@[^ \r\t\n\f@]+$")));
 79            parameters.Add(new SPParam("sitetemplate", "st", false, null, new SPNullOrNonEmptyValidator()));
 80            parameters.Add(new SPParam("lcid", "lcid", false, "0", new SPRegexValidator("^[0-9]+$")));
 81 
 82            StringBuilder sb = new StringBuilder();
 83            sb.Append("\r\n\r\nCreates a web application.\r\n\r\nParameters:\r\n");
 84            sb.Append("\t-url <url>\r\n");
 85            sb.Append("\t[-directory <virtual directory path>]\r\n");
 86            sb.Append("\t[-port <web application port>]\r\n");
 87            sb.Append("\t[-ownerlogin <domain\\name>]\r\n");
 88            sb.Append("\t[-owneremail <someone@example.com>]\r\n");
 89            sb.Append("\t[-exclusivelyusentlm]\r\n");
 90            sb.Append("\t[-ownername <display name>]\r\n");
 91            sb.Append("\t[-databaseuser <database user>]\r\n");
 92            sb.Append("\t[-databaseserver <database server>]\r\n");
 93            sb.Append("\t[-databasename <database name>]\r\n");
 94            sb.Append("\t[-databasepassword <database user password>]\r\n");
 95            sb.Append("\t[-lcid <language>]\r\n");
 96            sb.Append("\t[-sitetemplate <site template>]\r\n");
 97            sb.Append("\t[-donotcreatesite]\r\n");
 98            sb.Append("\t[-description <iis web site name>]\r\n");
 99            sb.Append("\t[-sethostheader <host header name>]\r\n");
100            sb.Append("\t[-apidname <app pool name>]\r\n");
101            sb.Append("\t[-apidtype <configurableid/NetworkService>]\r\n");
102            sb.Append("\t[-apidlogin <DOMAIN\\name>]\r\n");
103            sb.Append("\t[-apidpwd <app pool password>]\r\n");
104            sb.Append("\t[-allowanonymous]\r\n");
105            sb.Append("\t[-ssl]\r\n");
106            sb.Append("\t[-timezone <time zone ID>]\r\n");
107 
108            Init(parameters, sb.ToString());
109        }
110 
111        #region ISPStsadmCommand Members
112 
113        /// <summary>
114        /// Gets the help message.
115        /// </summary>
116        /// <param name="command">The command.</param>
117        /// <returns></returns>
118        public override string GetHelpMessage(string command)
119        {
120            return HelpMessage;
121        }
122 
123        /// <summary>
124        /// Runs the specified command.
125        /// </summary>
126        /// <param name="command">The command.</param>
127        /// <param name="keyValues">The key values.</param>
128        /// <param name="output">The output.</param>
129        /// <returns></returns>
130        public override int Execute(string command, System.Collections.Specialized.StringDictionary keyValues,
131                                    out string output)
132        {
133            output = string.Empty;
134 
135 
136            Uri uri = new Uri(Params["url"].Value);
137 
138            SPWebApplicationBuilder builder = GetWebAppBuilder(uri);
139 
140            SPWebApplication app = builder.Create();
141 
142            ProvisionTimerJob(app, false);
143 
144            // Set the TimeZone of the Application
145            if (Params["timezone"].UserTypedIn)
146                app.DefaultTimeZone = ushort.Parse(Params["timezone"].Value);
147 
148            app.Update();
149            app.Provision();
150 
151 
152            // Upload the newly created WebApplication to the List 'Web Application List' in Central Administration:
153            SPWebService.AdministrationService.WebApplications.Add(app);
154 
155            if (!Params["donotcreatesite"].UserTypedIn)
156            {
157                uint nLCID = uint.Parse(Params["lcid"].Value);
158                string webTemplate = Params["sitetemplate"].Value;
159                string ownerLogin = Params["ownerlogin"].Value;
160                ownerLogin = Utilities.TryGetNT4StyleAccountName(ownerLogin, app);
161                string ownerName = Params["ownername"].Value;
162                string ownerEmail = Params["owneremail"].Value;
163 
164                app.Sites.Add(uri.AbsolutePath, null, null, nLCID, webTemplate, ownerLogin, ownerName, ownerEmail, null,
165                              null,
166                              null);
167            }
168 
169 
170            Console.WriteLine(SPResource.GetString("PendingRestartInExtendWebFarm", new object[0]));
171            Console.WriteLine();
172 
173            if (!Params["donotcreatesite"].UserTypedIn)
174                Console.WriteLine(SPResource.GetString("AccessSiteAt", new object[] {uri.ToString()}));
175            Console.WriteLine();
176 
177            return OUTPUT_SUCCESS;
178        }
179 
180        private SPWebApplicationBuilder GetWebAppBuilder(Uri uri)
181        {
182            SPWebApplicationBuilder builder = new SPWebApplicationBuilder(SPFarm.Local);
183 
184            //Set the Port and the RootDirectory where you want to install the Application, e.g:
185            builder.Port = int.Parse(Params["port"].Value);
186            if (Params["directory"].UserTypedIn)
187                builder.RootDirectory = new DirectoryInfo(Params["directory"].Value);
188 
189            // Set the ServerComment for the Application which will be the Name of the Application in the SharePoint-List And IIS. If you do not set this Property, the Name of the Application will be 'SharePoint - <Default given Portnumber from System>' 
190            if (Params["description"].UserTypedIn)
191                builder.ServerComment = Params["description"].Value;
192 
193            // Create the content database for this Application
194            builder.CreateNewDatabase = true;
195            if (Params["databasename"].UserTypedIn)
196                builder.DatabaseName = Params["databasename"].Value;
197            if (Params["databaseserver"].UserTypedIn)
198                builder.DatabaseServer = Params["databaseserver"].Value;
199            if (Params["databaseuser"].UserTypedIn)
200                builder.DatabaseUsername = Params["databaseuser"].Value;
201            if (Params["databasepassword"].UserTypedIn)
202                builder.DatabasePassword = Params["databasepassword"].Value;
203 
204            // Host Header settings
205            if (Params["sethostheader"].UserTypedIn)
206            {
207                if (string.IsNullOrEmpty(Params["sethostheader"].Value))
208                    builder.HostHeader = uri.Host;
209                else
210                    builder.HostHeader = Params["sethostheader"].Value;
211            }
212            builder.DefaultZoneUri = uri;
213 
214 
215            // App pool settings
216            builder.ApplicationPoolId = Params["apidname"].Value;
217            if (Params["apidtype"].Value.ToLowerInvariant() == "networkservice")
218                builder.IdentityType = IdentityType.NetworkService;
219            else
220            {
221                builder.IdentityType = IdentityType.SpecificUser;
222                builder.ApplicationPoolUsername = Params["apidlogin"].Value;
223                builder.ApplicationPoolPassword = CreateSecureString(Params["apidpwd"].Value);
224            }
225 
226            // Some additional Settings
227            builder.UseNTLMExclusively = Params["exclusivelyusentlm"].UserTypedIn;
228            builder.AllowAnonymousAccess = Params["allowanonymous"].UserTypedIn;
229            builder.UseSecureSocketsLayer = Params["ssl"].UserTypedIn;
230            return builder;
231        }
232 
233        /// <summary>
234        /// Validates the specified key values.
235        /// </summary>
236        /// <param name="keyValues">The key values.</param>
237        public override void Validate(System.Collections.Specialized.StringDictionary keyValues)
238        {
239            bool isUserAccount;
240            if (Params["donotcreatesite"].UserTypedIn)
241            {
242                Params["ownerlogin"].Enabled = false;
243                Params["ownername"].Enabled = false;
244                Params["owneremail"].Enabled = false;
245                Params["sitetemplate"].Enabled = false;
246                Params["lcid"].Enabled = false;
247            }
248            else
249            {
250                if (Params["ownerlogin"].UserTypedIn)
251                {
252                    string ownerLogin = Utilities.TryGetNT4StyleAccountName(Params["ownerlogin"].Value, null);
253                    if (!SPUtility.IsLoginValid(null, ownerLogin, out isUserAccount))
254                        throw new ArgumentException(
255                            SPResource.GetString("InvalidLoginAccount", new object[] {ownerLogin}));
256                    if (!isUserAccount)
257                        throw new ArgumentException(SPResource.GetString("OwnerNotUserAccount", new object[0]));
258                }
259            }
260            if (Params["apidtype"].Value.ToLowerInvariant() == "networkservice")
261            {
262                Params["apidlogin"].Enabled = false;
263                Params["apidpwd"].Enabled = false;
264            }
265            else
266            {
267                if (Params["apidlogin"].UserTypedIn)
268                {
269                    string apidlogin = Utilities.TryGetNT4StyleAccountName(Params["apidlogin"].Value, null);
270                    if (!SPUtility.IsLoginValid(null, apidlogin, out isUserAccount))
271                        throw new ArgumentException(
272                            SPResource.GetString("InvalidLoginAccount", new object[] {apidlogin}));
273                }
274            }
275            base.Validate(keyValues);
276        }
277 
278        #endregion
279 
280        /// <summary>
281        /// Creates the secure string.
282        /// </summary>
283        /// <param name="strIn">The string to convert.</param>
284        /// <returns></returns>
285        internal static SecureString CreateSecureString(string strIn)
286        {
287            if (strIn != null)
288            {
289                SecureString str = new SecureString();
290                foreach (char ch in strIn)
291                {
292                    str.AppendChar(ch);
293                }
294                str.MakeReadOnly();
295                return str;
296            }
297            return null;
298        }
299 
300        private static void ProvisionTimerJob(SPWebApplication application, bool resetIis)
301        {
302            if (SPFarm.Local.TimerService.Instances.Count > 1)
303            {
304                // SPWebApplicationProvisioningJobDefinition is an internal class so we need to use reflection to set it.
305 
306                // SPWebApplicationProvisioningJobDefinition definition = new SPWebApplicationProvisioningJobDefinition(application, resetIis);
307                Type SPWebApplicationProvisioningJobDefinition =
308                    Type.GetType(
309                        "Microsoft.SharePoint.Administration.SPWebApplicationProvisioningJobDefinition, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
310 
311                ConstructorInfo SPWebApplicationProvisioningJobDefinitionConstructor =
312                    SPWebApplicationProvisioningJobDefinition.GetConstructor(
313                        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
314                        null,
315                        new Type[] {typeof (SPWebApplication), typeof (bool)}, null);
316                object jobDef =
317                    SPWebApplicationProvisioningJobDefinitionConstructor.Invoke(new object[] {application, resetIis});
318 
319 
320                // jobDef.Schedule = new SPOneTimeSchedule(DateTime.Now);
321                PropertyInfo scheduleProp = SPWebApplicationProvisioningJobDefinition.GetProperty("Schedule",
322                                                                                                  BindingFlags.
323                                                                                                      FlattenHierarchy |
324                                                                                                  BindingFlags.NonPublic |
325                                                                                                  BindingFlags.Instance |
326                                                                                                  BindingFlags.
327                                                                                                      InvokeMethod |
328                                                                                                  BindingFlags.
329                                                                                                      GetProperty |
330                                                                                                  BindingFlags.Public);
331 
332                scheduleProp.SetValue(jobDef, new SPOneTimeSchedule(DateTime.Now), null);
333 
334                // jobDef.Update();
335                MethodInfo update = SPWebApplicationProvisioningJobDefinition.GetMethod("Update",
336                                                                                        BindingFlags.NonPublic |
337                                                                                        BindingFlags.Public |
338                                                                                        BindingFlags.Instance |
339                                                                                        BindingFlags.InvokeMethod |
340                                                                                        BindingFlags.FlattenHierarchy,
341                                                                                        null,
342                                                                                        new Type[] {}, null);
343 
344 
345                update.Invoke(jobDef, new object[] {});
346            }
347            SPAdministrationWebApplication local = SPAdministrationWebApplication.Local;
348            if (local != null)
349            {
350                local.RemoveAdministrativeTask("WSSCreateWebApplication");
351            }
352        }
353    }
354}

The syntax of the command can be seen below:

C:\>stsadm -help gl-createwebapp

stsadm -o gl-createwebapp

Creates a web application.

Parameters:
        -url <url>
        [-directory <virtual directory path>]
        [-port <web application port>]
        [-ownerlogin <domain\name>]
        [-owneremail <someone@example.com>]
        [-exclusivelyusentlm]
        [-ownername <display name>]
        [-databaseuser <database user>]
        [-databaseserver <database server>]
        [-databasename <database name>]
        [-databasepassword <database user password>]
        [-lcid <language>]
        [-sitetemplate <site template>]
        [-donotcreatesite]
        [-description <iis web site name>]
        [-sethostheader <host header name>]
        [-apidname <app pool name>]
        [-apidtype <configurableid/NetworkService>]
        [-apidlogin <DOMAIN\name>]
        [-apidpwd <app pool password>]
        [-allowanonymous]
        [-ssl]
        [-timezone <time zone ID>]

The following table summarizes the command and its various parameters:

Command NameAvailabilityBuild Date
gl-createwebappWSS v3, MOSS 2007Released: 10/31/2007, Updated: 8/9/2008
Parameter NameShort FormRequiredDescriptionExample Usage
urlYesThe load balanced URL is the domain name for all sites users will access in this SharePoint Web application. This URL domain will be used in all links shown on pages within the web application.-url http://portal
directorydirNoThe virtual directory of the web application. If not specified then the directory will be c:\inetpub\wwwroot\wss\virtualdirectories{hostheader}{port}-directory c:\moss\webs\portal, -dir c:\moss\webs\portal`
portpNoThe port number for the web application. The default is 80.-port 123, -p 123
ownerloginolYes – unless -donotcreatesite is passed inThe login name of the owner of the site if a root site collection is to be created. This parameter is disabled if -donotcreatesite is passed in.-ownerlogin domain\user1, -ol domain\user1
owneremailoeYes – unless -donotcreatesite is passed inThe email address of the owner of the site collection. This parameter is disabled if -donotcreatesite is passed in.-owneremail user1@domain.com, -oe <a href="mailto:user1@domain.com">user1@domain.com</a>
exclusivelyusentlmntlmNoSets the site to use NTLM Windows Authentication. If omitted Kerberos authentication will be enabled.-exclusivelyusentlm, -ntlm
ownernameonNoThe name of the owner of the root site collection if created. This parameter is disabled if -donotcreatesite is passed in.-ownername "Gary Lapointe", -on "Gary Lapointe"
databaseuserduNoThe SQL Server account to use to connect to the content database. If omitted then Windows Authentication will be used. It is recommended that you use Windows Authentication and not a SQL Server account.-databaseuser spcontent, -du spcontent
databasepassworddpNo – unless databaseuser is provided.The password of the database user specified by the -databaseuser parameter.-databasepassword pa$$w0rd, -dp pa$$w0rd
databasenamednNoThe name of the content database. If not specified then a database will be created using “SharePoint – #” where # is a random number.-databasename SharePoint_Portal_Content, -dn SharePoint_Portal_Content
databaseserverdsNoThe name of the database server on which to store the content database.-databaseserver spsql1, -ds spsql1
lcidNoThe local ID of the root site collection.-lcid 1033
sitetemplatestNoThe template to use when creating the root site collection.-sitetemplate SPSPORTAL#0, -st SPSPORTAL#0
donotcreatesitenositeNoIf provided then a root site collection will not be created.-donotcreatesite, -nosite
descriptiondescNoThe description that will be used to identify the web application in IIS.-description "SharePoint Portal (80)", -desc "SharePoint Portal (80)"
sethostheadersethhNoSets the host header of the site. The value is optional and if omitted the host of the -url parameter will be used.-sethostheader portal, -sethh portal
apidnameapidNoThe name of the application pool. If not provided then “DefaultAppPool” will be used.-apidname SharePoint_Portal_AppPool, -apid SharePoint_Portal_AppPool
apidtypeNoEither networkservice or configurableid. Defaults to networkservice. If the type is set to configurableid then apidlogin and apidpwd is required.-apidtype configurableid
apidloginapuNo – unless apidtype is configurableidThe user account that the application pool will run as.-apidlogin "domain\spportalapppool", -apu "domain\spportalapppool"
apidpwdappNo – unless apidtype is configurableidThe password for the application pool user account.-apidpwd pa$$w0rd, -app pa$$w0rd
allowanonymousanonNoAllows anonymous access to the web application.-allowanonymous, -anon
sslNoConfigures the web application to use SSL.-ssl
timezonetzNoThe time zone to associate the web application with-timezone 12, -tz 12

Here’s an example of how to create a web application using minimal parameters:

stsadm –o gl-createwebapp -url "http://webappname" -donotcreatesite

Running this command will create a content database using the name “SharePoint – {random #}”, it will name the web application the same as the database and will use c:\inetpub\wwwroot\wss\virtualdirectories\{random #}. It will also use the DefaultAppPool application pool.

Here’s another example which uses most of the parameters to set as many options as possible:

stsadm -o gl-createwebapp -url "http://testwebapp" -directory "c:\moss\webs\testwebapp" -port 80 -ownerlogin "domain\user1" -owneremail "someone@example.com" -exclusivelyusentlm -databasename "SharePoint_TestWebApp" -sitetemplate "SPS#0" -description "SharePoint_TestWebApp_80" -sethostheader testwebapp -apidname "AppPool1" -apidtype configurableid -apidlogin "domain\user2" -apidpwd "password" -timezone 10

Running this command will create a new application pool named “AppPool1”, create a content database named “SharePoint_TestWebApp”, name the web application “SharePoint_TestWebApp_80”, set the time zone to Eastern, and set the virtual directory path to “c:\moss\webs\testwebapp”.

Update 8/9/2008: I’ve fixed an issue where I wasn’t setting the DefaultZoneUri property of the SPWebApplicationBuilder object (corresponds to the Load Balanced URL setting in the UI). So now the URL that is passed in via the -url parameter will be used to set the load balanced URL (DefaultZoneUri) and if you wish to set a host header you now will need to pass it in as an argument via the -sethostheader parameter (if the value is omitted it will use the host specified in the -url parameter). I’ve also added some code that I had previously omitted which creates a timer job to aid in the provisioning of the web application on other servers in the farm. The code now does exactly what the UI code does so hopefully it will address the issues some have reported.