I've moved my blog to!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Wednesday, October 31, 2007

Delete Web Application

I had just finished creating the gl-createwebapp command and was about to go and delete the test web applications that I had created while testing the command and I discovered that there is no built in stsadm command for deleting a web application. I took a look at the code that is called when deleting via the browser and decided that it was simple enough that I could recreate it as a command - can't have a create without a delete :).  The command I created is called gl-deletewebapp.

The only tricky spot I had was that there was a couple of pieces of code that required the use of internal classes - fortunately I'm getting pretty good at making calls to these internal classes so it didn't really slow me down much. The main hang up was that the Unprovision() method of the SPWebApplication object doesn't allow you to specify whether you want to delete the IIS web site or not - it just defaults to deleting it.

I wanted this command to function like the browser equivalent so I had to use the internal UnprovisionIisWebSites() method along with the RetractSolutions method of the SPSolutions class. And to make sure that I was mirroring the code executed when using the browser I had to also set a timer job using an internal only class (there's gotta be a way around this one). The code to do all this is below:

   1: public override int Run(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
   2: {
   3:  output = string.Empty;
   5:  InitParameters(keyValues);
   7:  string url = Params["url"].Value;
   8:  bool deleteContent = Params["deletecontentdb"].UserTypedIn;
   9:  bool deleteIis = Params["deleteiiswebsite"].UserTypedIn;
  11:  SPWebApplication webApp = SPWebApplication.Lookup(new Uri(url));
  12:  if (webApp.IisSettings.Count <= 0 && deleteContent)
  13:  {
  14:   DeleteContentDBs(webApp);
  15:   webApp.Delete();
  16:   return 1;
  17:  }
  19:  int index = 0;
  20:  string[] serverComments = new string[webApp.IisSettings.Count];
  21:  string[] vdirs = new string[webApp.IisSettings.Count];
  22:  foreach (SPIisSettings iisSetting in webApp.IisSettings.Values)
  23:  {
  24:   vdirs[index] = iisSetting.Path.ToString();
  25:   serverComments[index] = iisSetting.ServerComment;
  26:   index++;
  27:  }
  29:  // webApp.Unprovision() does not allow us to specify whether we want the site deleted so we have to use the internal version.
  30:  // SPWebApplication.UnprovisionIisWebSites(deleteIis, serverComments, webApp.ApplicationPool.Name);
  31:  MethodInfo unprovisionIisWebSites = webApp.GetType().GetMethod("UnprovisionIisWebSites",
  32:          BindingFlags.NonPublic | BindingFlags.Public |
  33:          BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Static,
  34:          null, new Type[] {typeof(bool), typeof(string[]), typeof(string)}, null);
  36:  unprovisionIisWebSites.Invoke(webApp, new object[] { deleteIis, serverComments, webApp.ApplicationPool.Name });
  39:  // SPSolution.RetractSolutions(webApp.Farm, webApp.Id, vdirs, serverComments, true);
  40:  MethodInfo retractSolutions = typeof(SPSolution).GetMethod("RetractSolutions",
  41:       BindingFlags.NonPublic | BindingFlags.Public |
  42:       BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Static,
  43:       null, new Type[] {typeof(SPFarm), typeof(Guid), typeof(string[]), typeof(string[]), typeof(bool)}, null);
  45:  retractSolutions.Invoke(null, new object[] { webApp.Farm, webApp.Id, vdirs, serverComments, true });
  48:  if (SPFarm.Local.TimerService.Instances.Count > 1)
  49:  {
  50:   // SPIisWebsiteUnprovisioningJobDefinition is an internal class so we need to use reflection to set it.
  52:   // SPIisWebsiteUnprovisioningJobDefinition jobDef = new SPIisWebsiteUnprovisioningJobDefinition(deleteIis, serverComments, webApp.ApplicationPool.Name, vdirs, webApp.Id, true);
  53:   Type sPIisWebsiteUnprovisioningJobDefinitionType = Type.GetType("Microsoft.SharePoint.Administration.SPIisWebsiteUnprovisioningJobDefinition, Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
  55:   ConstructorInfo sPIisWebsiteUnprovisioningJobDefinitionConstructor =
  56:    sPIisWebsiteUnprovisioningJobDefinitionType.GetConstructor(
  57:     BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
  58:     null,
  59:     new Type[] {typeof(bool), typeof(string[]), typeof(string), typeof(string[]), typeof(Guid), typeof(bool)}, null);
  60:   object jobDef = sPIisWebsiteUnprovisioningJobDefinitionConstructor.Invoke(new object[] { deleteIis, serverComments, webApp.ApplicationPool.Name, vdirs, webApp.Id, true });
  63:   // jobDef.Schedule = new SPOneTimeSchedule(DateTime.Now);
  64:   PropertyInfo scheduleProp = sPIisWebsiteUnprovisioningJobDefinitionType.GetProperty("Schedule",
  65:               BindingFlags.FlattenHierarchy |
  66:               BindingFlags.NonPublic |
  67:               BindingFlags.Instance |
  68:               BindingFlags.InvokeMethod |
  69:               BindingFlags.GetProperty |
  70:               BindingFlags.Public);
  72:   scheduleProp.SetValue(jobDef, new SPOneTimeSchedule(DateTime.Now), null);
  74:   // jobDef.Update();
  75:   MethodInfo update = sPIisWebsiteUnprovisioningJobDefinitionType.GetMethod("Update",
  76:              BindingFlags.NonPublic |
  77:              BindingFlags.Public |
  78:              BindingFlags.Instance |
  79:              BindingFlags.InvokeMethod |
  80:              BindingFlags.FlattenHierarchy,
  81:              null,
  82:              new Type[] { }, null);
  85:   update.Invoke(jobDef, new object[] { });
  86:  }
  88:  if (deleteContent)
  89:   DeleteContentDBs(webApp);
  91:  webApp.Delete();
  93:  return 1;
  94: }
  97: private static void DeleteContentDBs(SPWebApplication webApp)
  98: {
  99:  foreach (SPContentDatabase db in webApp.ContentDatabases)
 100:  {
 101:   db.Unprovision();
 102:  }
 103: }
The syntax of the command can be seen below:
C:\>stsadm -help gl-deletewebapp

stsadm -o gl-deletewebapp

Deletes a web application.

Here's an example of how to delete a web application without deleting the IIS web site or the content database:
stsadm –o gl-deletewebapp -url "http://webappname"
Here's another example which will delete the IIS web site and the content database:
stsadm -o gl-deletewebapp -url "http://webappname" -deleteiiswebsite -deletecontentdb


Anonymous said...

This is sweet.

It would be nice though if you could specify a farm item to delete, e.g,

stsadm -o gl-deletewebapp -item "Farm\Windows SharePoint Services Web Application\webappname" -deleteiiswebsite -deletecontentdb


Anonymous said...

Hmm, when I specify -deleteiiswebsite, the site does not get deleted, though it did remove a host header. Any ideas?

Gary Lapointe said...

Not really sure why it's not working. Any chance you can step through the code or add some Console.WriteLine statements to see what the execution path is? Are you getting any errors in the ULS logs or event log?

babeach said...

I'm somewhat new to WSS. How do I import your webapp into my environment so I can try it?

I have the problem where I tried extending a site, provisioning failed, and now I can't extend or create any other site.
Want to see if this will clean things up so I din't have to re-install everything.

babeach said...

Never mind, I'm an idiot. Was excited you may have a fix for me and negated to browse the rest of the page.

babeach said...

Never mind, I'm an idiot. Was excited you may have a fix for me and negated to browse the rest of the page.

Gary Lapointe said...

Download the WSP file for your version (WSS or MOSS). Then run the following commands (using the MOSS version as an example):

stsadm -o addsolution -filename c:\Lapointe.SharePoint.STSADM.Commands.WSP

stsadm -o deploysolution -name Lapointe.SharePoint.STSADM.Commands.WSP -immediate -allowgacdeployment

stsadm -o execadmsvcjobs