Unable to create Website using powershell in window service - c#-4.0

I Want to create a website in IIS using powershell script.I write this code in a window service.
protected override void OnStart(string[] args)
{
string sitename = "rajesh";
//To create a New site
//Powershell script
string script = "cd\\\n" + "import-Module WebAdministration \n" + "IIS:\n" + "New-item iis:\\Sites\\rajesh -PhysicalPath C:\\inetpub\\wwwroot\test -bindings #{Protocol='http';bindingInformation='*:8080:" + sitename + "'}" + "\n add-content C:\\Windows\\System32\\drivers\\etc\\Hosts '127.0.0.1 " + sitename + "'";
string s = RunScript(script);
}
private static string RunScript(string scriptText)
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
But when i try to start this window service in services listing...It throws an error.. The services on locat computer start and stopped..... When i change the powershell script to Create a folder or some small task then this window service is working but when i try to create a website through this powershell..it throws an error and window service can't be started.This powershell script is working in powershell.But not in window service..

The error produced would be helpful.
Sitename isn't a variable either, is that atypo here or in the code?
You could try replacing the \n with ; as well, powershell doesn't use that as new line, though i guess the C# is.

There's a couple of things to note here and I think you maybe don't understand some aspects of Windows Services:
OnStart is not the place to be putting any kind of logic. This event is there so you can initialise the service. For example starting a background worker thread that would run the main logic of the service and respond to external requests. For example - starting a TCP listener or initialising .NET Remoting or WCF.
OnStart is only called once when the service starts.
OnStart must return within 30 seconds although your initialisation code can request additional time.
You're calling out to PowerShell and the WebAdministration add-in to create websites. Whilst this is doable, it's clunky, perhaps you should consider calling directly into the IIS7 Managed API's:
Microsoft.Web.Administration Namespace
Here's an example of creating a site using the managed API's:
using (ServerManager serverManager = new ServerManager())
{
Site site = serverManager.Sites.Add("My Web Site", "C:\\inetpub\\wwwroot\test", 80);
site.Bindings.Clear();
site.Bindings.Add("*:8080:", "http");
serverManager.CommitChanges();
}
It should also be noted that any configuration tasks performed on IIS require that the account doing this have elevated privileges i.e. be an Administrator or if it's a service run under the SYSTEM account.

Related

Deploying to a private IIS server from a build in Visual Studio Team Services

Having done what is suggested here: Deploy from Visual Studio Online build to private IIS server
... how do I setup automatic deploys as part of my build when I build a whole branch **/*.sln?
What I have tried ...
In VS I can get the latest version of the code, open a solution and then ...
right click > publish > pick publish profile > deploy
I have named my publish profiles things like "dev", "qa", "production", these refer to the environments into which the project will be deployed and the profiles contain all of the configuration information needed for VS to deploy (via webdeploy / msdeploy) using "one click deploy" that application.
I want to have Team Services on the build server do the exact same thing for projects that have publish profiles defined after it's built the code.
My understanding was that I could just add the msbuild args like this ...
this results in the deployment part of the build throwing the following exception in to the build log ...
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.targets(4288,5):
Error ERROR_USER_NOT_ADMIN: Web deployment task failed.
(Connected to 'server' using the Web Deployment Agent Service, but could not authorize. Make sure you are an administrator on 'server'.
Learn more at: http://go.microsoft.com/fwlink/?LinkId=221672#ERROR_USER_NOT_ADMIN.)
What user is this using if not the user defined in the publish profile?
Related issues:
Publishing via TFS Build Service fails with "User not Admin"
TFS Builds: Running the builds as administrator
I added an account to the server in question (since the build and server to be deployed to are the same server it made things easier), I also added a group to the server called "MSDepSvcUsers" and added the new account in question to it and the admins group on the box.
I then told both the Web Deployment Agent service and the Team Services Agent service to run under this account (and restarted them).
Unfortunately the result is the same ... I now really want to know how I go about ensuring the account that is used for the msdeploy command is something I expect without relying on loads of scripting ... or maybe that's why Microsoft haven't set this up as a default deploy step option in Team Services already!
Ok so I had some long conversations with the VSTS team over at Microsoft about this and the long and short of it is ...
Microsoft:
We understand your frustration with this area and a big project is
about to spin up to resolve this issue
...
Me being me, came up with some "trick to make it happen".
I managed to figure out that the build box for some odd reason can't be the same server that you are deploying too (no idea why) but having figured that out I wrote a simple console app that with some additional feedback from Microsoft came out pretty good.
It even reports progress back to the process and can log exceptions in the deployment as exceptions in order to fail the build by calling up "internal commands" (neat how this works by the way kudos to the team for that).
There are some hacks in here and it's not perfect but hopefully it'll help someone else, I call this because it's part of the code that gets built in my repo so I am able to add a step in to the build process to call this from within the build output passing the environment name I want to deploy to.
This in tern grabs all the packages (as per the settings above) and uses their publish profiles to figure out where the packages need to go and sends them to the right servers to be deployed ...
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Deploy
{
class Program
{
static string msDeployExe = #"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe";
static void Main(string[] args)
{
var env = args[0];
var buildRoot = Path.Combine(Assembly.GetExecutingAssembly().Location.Replace("Deploy.exe", ""), env);
//var commands = GetCommands(buildRoot);
var packages = new DirectoryInfo(buildRoot).GetFiles("*.zip", SearchOption.AllDirectories);
bool success = true;
for (int i = 0; i < packages.Length; i++)
{
if (!Deploy(packages[i], env)) success = false;
Console.WriteLine("##vso[task.setprogress]" + (int)(((decimal)i / (decimal)packages.Length) * 100m));
}
Console.WriteLine("##vso[task.setprogress]100");
if(success) Console.WriteLine("##vso[task.complete result=Succeeded]");
else Console.WriteLine("##vso[task.complete result=SucceededWithIssues]");
}
static bool Deploy(FileInfo package, string environment)
{
bool succeeded = true;
Console.WriteLine("Deploying " + package.FullName);
var procArgs = new ProcessStartInfo
{
FileName = msDeployExe,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
Arguments =
"-source:package='" + package.FullName + "' " +
"-dest:auto,ComputerName='" + environment + ".YourDomain.com',UserName='deployment user',Password='password',AuthType='ntlm',IncludeAcls='False' " +
"-verb:sync " +
"-disableLink:AppPoolExtension " +
"-disableLink:ContentExtension " +
"-disableLink:CertificateExtension " +
"-setParamFile:\"" + package.FullName.Replace("zip", "SetParameters.xml") + "\""
};
try
{
Console.WriteLine(msDeployExe + " " + procArgs.Arguments);
using (var process = Process.Start(procArgs))
{
var result = process.StandardOutput.ReadToEnd().Split('\n');
var error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (!string.IsNullOrEmpty(error))
{
Console.WriteLine("##vso[task.logissue type=error]" + error);
succeeded = false;
}
foreach (var l in result)
if (l.ToLowerInvariant().StartsWith("error"))
{
Console.WriteLine("##vso[task.logissue type=error]" + l);
succeeded = false;
}
else
Console.WriteLine(l);
}
}
catch (Exception ex) {
succeeded = false;
Console.WriteLine("##vso[task.logissue type=error]" + ex.Message);
Console.WriteLine("##vso[task.logissue type=error]" + ex.StackTrace);
}
return succeeded;
}
}
}
No you don't need a ton of PS scripts to achieve this. MSDeploy.exe is an incredibly useful tool that can probably cover your needs. Add the /t:Package build argument to your VS build task to create a package. Then use a Commandline task to deploy the MSDeploy package to your IIS site. Here are more details about out WebDeploy/MSDeploy works:
http://www.dotnetcatch.com/2016/02/25/the-anatomy-of-a-webdeploy-package/
I do this all of the time. What I did was setup a Release in the Release tab and signed up to enable Deployment Groups. Once you have a the Deployment Group enabled on your account (needed contact MS to get this enabled). I could download PS script that I run on each of the machines that I want to deploy to. Then in the Release screen I can setup the steps to run in a Deployment Group and then the various publish tasks run on the local server allowing them to work.
Using the Deployment Groups is an excellent solution because if you have it load balanced it will deploy to only a portion of the load balanced servers at a time. Allowing the app to stay up the whole time.

Restart Web/Api-App on Azure programmatically

How can I restart Web-Apps and API-Apps on Azure programmatically?
(I'd like to call it from another API-App within the same App service plan.)
There's also the "Microsoft Azure Management Libraries" Nuget that allows you to work with Azure services from inside of applications.
See this page for an example on how to create new web sites from inside of an Azure Web site. Restarting web services work in a similar way to creating new services. See this page for a list of available web site related methods.
Also, for authenticating is used certificate base authentication, see this page for more details on that.
Bellow is a short command line program that will restart all websites in all the webspaces you got in your Azure subscription. It works kinda like an iisreset for Azure Web Sites.
The code is based on samples taken from the links earlier mentioned:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Management.WebSites;
using Microsoft.WindowsAzure;
using System.Security.Cryptography.X509Certificates;
using Microsoft.WindowsAzure.Management.WebSites.Models;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var subscriptionId = "[INSERT_YOUR_SUBSCRIPTION_ID_HERE]";
var cred = new CertificateCloudCredentials(subscriptionId, GetCertificate());
var client = new WebSiteManagementClient(cred);
WebSpacesListResponse webspaces = client.WebSpaces.List();
webspaces.Select(p =>
{
Console.WriteLine("Processing webspace {0}", p.Name);
WebSpacesListWebSitesResponse websitesInWebspace = client.WebSpaces.ListWebSites(p.Name,
new WebSiteListParameters()
{
});
websitesInWebspace.Select(o =>
{
Console.Write(" - Restarting {0} ... ", o.Name);
OperationResponse operation = client.WebSites.Restart(p.Name, o.Name);
Console.WriteLine(operation.StatusCode.ToString());
return o;
}).ToArray();
return p;
}).ToArray();
if(System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine("Press anykey to exit");
Console.Read();
}
}
private static X509Certificate2 GetCertificate()
{
string certPath = Environment.CurrentDirectory + "\\" + "[NAME_OF_PFX_CERTIFICATE]";
var x509Cert = new X509Certificate2(certPath,"[PASSWORD_FOR_PFX_CERTIFICATE]");
return x509Cert;
}
}
}
Another alternative, if you can't find the function you need from the above mentioned library, you can also run powershell commands programmatically from inside of your application. You most likely will need to move, the application that is supposed to run these cmdlets, to a virtual machine to be able to load the needed powershell modules. See this page for more information on running powershell cmdlets programmatically.
You can use Powershell to do this. The relevant commands are:
Start-AzureWebsite -Name “xxxx”
Stop-AzureWebsite -Name “xxxx”
You can find help on these commands at the following links:
https://msdn.microsoft.com/en-us/library/azure/dn495288.aspx
https://msdn.microsoft.com/en-us/library/azure/dn495185.aspx
I think handling the base REST API is much better option. The Azure SDK changes quite a lot and lacks good documentation.
Here is an up-to-date sample code:
https://github.com/davidebbo/AzureWebsitesSamples/
You can adapt it to your needs.

Force Application Start on Azure Web Role

I have a web role on azure and I would like to force a Application_Start without waiting for the first request.
I managed to set the "Start Automatically" property to true on my site
AutoStart a WCF on Azure WebRole
But the Application_Start is not called until the first request comes.
I don't know exactly if I am missing something important here. The server is a W2008 R2 and the IIS version is 7.5
Thanks!
SOLUTION
I put the solution code here. I hope will help someone. I just added a WebRole.cs and just put that code to perform a ping every 30 seconds. Please netice I'm browsing Service.svc because this is my endpoint, your endpoint could be another one. Notice I'm asking for "Endpoint1". If you have more than one endpoints, you should review that line.
public class WebRole : RoleEntryPoint
{
public override void Run()
{
var localuri = new Uri( string.Format( "http://{0}/Service.svc", RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint1"].IPEndpoint ) );
while (true)
{
try
{
var request = (HttpWebRequest)WebRequest.Create(localuri);
request.Method = "GET";
var response = request.GetResponse();
}
catch { }
System.Threading.Thread.Sleep(30000);
}
}
public override bool OnStart()
{
return base.OnStart();
}
}
The IIS will only start when the first request arrives. The workaround is to send an HTTP request to the same VM from within OnStart or your RoleEntryPoint descendant - that's easy using WebRequest or equivalent class.
Jordi, I've recently experienced the same issue.
Based on my test Application_Start() is called ONLY when the 1st request ISS for the WebApp. (if you try to start VS in Debug without it open any page (see options in proj/debug), you will see that Application_Start() won't be called also if you don't run the WebApp in Azure)
I suppose that you need doing somethings when the WebRole start, well put your code in the WebRole.cs ;)
Here you can override OnStart() and OnStop() and put your code that wiil be execuded when the WebRole will start.
I've used this way to run a BakgroundWorker that do some scheduled tasks, independently from IIS.
I hope this help.
Davide.
Note:
1 - if you dont'have a WebRole.cs create it in the root of project and write inside:
public class WebRole : RoleEntryPoint
{
public override bool OnStart()
{
...your code...
return base.OnStart();
}
}
2 - If you need to debug the code mind that you need to run VS in debug with the Azure project that refer to WebApp as a "Run Project", otherwise the WebRole will not be called
You could try putting some code in your WebRole.cs to request some URLs from your website. I've tried that, and it seems to work somewhat. But it's a pain to debug, so I never got it really nailed down.
Another option would be to use IIS Application Initialization. You can't use it on IIS 7.5, but you can get IIS 8 if you upgrade your roles to Windows 2012 (set osFamily="3" in your .cscfg).

How to Create a Managed Path through SharePoint Object Model

This is a question for a WSS/SharePoint guru.
Consider this scenario: I have an ASP.Net web service which links our corporate CRM system and WSS-based intranet together. What I am trying to do is provision a new WSS site collection whenever a new client is added to the CRM system. In order to make this work, I need to programmatically add the managed path to the new site collection. I know that this is possible via the Object Model, but when I try it in my own web service, it fails. Sample code extract below:
Dim _ClientSiteUrl As String = "http://myintranet/clients/sampleclient"
Using _RootWeb As SPSite = New SPSite("http://myintranet")
Dim _ManagedPaths As SPPrefixCollection = _RootWeb.WebApplication.Prefixes
If Not (_ManagedPaths.Contains(_ClientSiteUrl)) Then
_ManagedPaths.Add(_ClientSiteUrl, SPPrefixType.ExplicitInclusion)
End If
End Using
This code fails with a NullReferenceException on SPUtility.ValidateFormDigest(). Research suggested that this may be due to insufficient privileges, I tried running the code within an elevated privileges block using SPSecurity.RunWithElevatedPrivileges(AddressOf AddManagedPath), where AddManagedPath is a Sub procedure containing the above code sample.
This then fails with an InvalidOperationException, "Operation is not valid due to the current state of the object."
Where am I going wrong?
One workaround I have managed to do is to call out to STSADM.EXE via Process.Start(), supplying the requisite parameters, and this works.
Update: whilst developing the web service, I am running it using the built-in Visual Studio 2005 web server - what security context will this be running under? Can I change the security context by putting entries in web.config?
Update: I think the problem is definitely to do with not running the web service within the correct SharePoint security context. I decided to go with the workaround I suggested and shell out to STSADM, although to do this, the application pool identity that the web service runs under must be a member of the SharePoint administrators.
Update
I think you have proved that the issue is not with the code.
SPSecurity.RunWithElevatedPrivileges: Normally the code in the SharePoint web application executes with the privileges of the user taking the action. The RunWithElevatedPrivileges runs the code in the context of the SharePoint web application pools account (i think)
The description on MSDN could go into the details a tiny bit more.
The issue with the call may be that the web service is not actually running the code within a SharePoint process, so explaining why it cannot elevate (wild guess alert).
Have a crack at changing the user of your web services application pool and see if that gives any joy.
It is likely to be a permissions issue.
Maybe try:
Dim clientSiteUrl As String = "http://myintranet/clients/sampleclient"
Using SPSite = new SPSite(clientSiteUrl)
webApp As SPWebApplication = SPWebApplication.Lookup(new Uri(clientSiteUrl));
If Not (webApp.Prefixes.Contains(clientSiteUrl)) Then
webApp.Prefixes.Add(clientSiteUrl, SPPrefixType.ExplicitInclusion)
End If
End Using
This is not exact code.
Since the above code is not the exact code, here is the exact working code for a Web Application scopped feature in the Feature Activated event:
On feature activation at the Mange web application features page, activate feature will create a new Explicit managed path in the specified web application (I want to replace the hard coding, maybe with Properties.Feature.Parent, or something similar.)
using (SPSite site = new SPSite("http://dev-moss07-eric/PathHere")) {
SPWebApplication webApp = SPWebApplication.Lookup(new Uri("http://dev-moss07-eric"));
if (webApp.Prefixes.Contains("PathHere"))
{
//
}
else
{
webApp.Prefixes.Add("PathHere", SPPrefixType.ExplicitInclusion);
}
}
Code can probably be improved, but its my attempt at converting the above code.
If you want to create a managed path (explicit) and a site collection at that path, do the following:
using (SPSite site = new SPSite("http://dev-moss07-eric")) {
SPWebApplication webApp = SPWebApplication.Lookup(new Uri("http://dev-moss07-eric"));
if (webApp.Prefixes.Contains("ManagedPathHere"))
{
//
}
else
{
webApp.Prefixes.Add("ManagedPathHere", SPPrefixType.ExplicitInclusion);
}
using (SPWeb web = site.OpenWeb())
{
SPWebApplication webApplication = web.Site.WebApplication;
try
{
webApplication.Sites.Add("ManagedPathHere","Site Title Here","This site is used for hosting styling assets.", 1033, "STS#1", "6scdev\\eric.schrader", "Eric Schrader", "eric.schrader#6sc.com");
}
catch (Exception ex)
{
//ex.ToString;
}
}
}

Sharepoint: executing stsadm from a timer job + SHAREPOINT\System rights

I have an unusual situation in which I need a SharePoint timer job to both have local administrator windows privileges and to have SHAREPOINT\System SharePoint privileges.
I can get the windows privileges by simply configuring the timer service to use an account which is a member of local administrators. I understand that this is not a good solution since it gives SharePoint timer service more rights then it is supposed to have. But it at least allows my SharePoint timer job to run stsadm.
Another problem with running the timer service under local administrator is that this user won't necessarily have SHAREPOINT\System SharePoint privileges which I also need for this SharePoint job. It turns out that SPSecurity.RunWithElevatedPrivileges won't work in this case. Reflector shows that RunWithElevatedPrivileges checks if the current process is owstimer (the service process which runs SharePoint jobs) and performs no elevation this is the case (the rational here, I guess, is that the timer service is supposed to run under NT AUTHORITY\NetworkService windows account which which has SHAREPOINT\System SharePoint privileges, and thus there's no need to elevate privileges for a timer job).
The only possible solution here seems to be to run the timer service under its usual NetworkService windows account and to run stsadm as a local administrator by storing the administrator credentials somewhere and passing them to System.Diagnostics.Process.Run() trough the StarInfo's Username, domain and password.
It seems everything should work now, but here is another problem I'm stuck with at the moment. Stsamd is failing with the following error popup (!) (Winternals filemon shows that stsadm is running under the administrator in this case):
The application failed to initialize properly (0x0c0000142).
Click OK to terminate the application.
Event Viewer registers nothing except the popup.
The local administrator user is my account and when I just run stsadm interactively under this account everything is ok. It also works fine when I configure the timer service to run under this account.
Any suggestions are appreciated :)
I'm not at work so this is off the top of my head, but: If you get a reference to the Site, can you try to create a new SPSite with the SYSTEM-UserToken?
SPUserToken sut = thisSite.RootWeb.AllUsers["SHAREPOINT\SYSTEM"].UserToken;
using (SPSite syssite = new SPSite(thisSite.Url,sut)
{
// Do what you have to do
}
The SharePoint Timer jobs runs with the SharePoint Firm Admin credentials since, the information get into the SharePoint Config Database. Thus the application pool will not have the access.
For testing the timer job in dev environment, we can temporarily change the application pool account to the application pool account being used for Central Administration.
Other applications if run this way (i.e. from a timer job with explicit credentials) are failing the same way with "The application failed to initialize propely". I just worte a simple app which takes a path of another executable and its arguments as parameres and when run from that timer job it fails the same way.
internal class ExternalProcess
{
public static void run(String executablePath, String workingDirectory, String programArguments, String domain, String userName,
String password, out Int32 exitCode, out String output)
{
Process process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
StringBuilder outputString = new StringBuilder();
Object synchObj = new object();
DataReceivedEventHandler outputAppender =
delegate(Object sender, DataReceivedEventArgs args)
{
lock (synchObj)
{
outputString.AppendLine(args.Data);
}
};
process.OutputDataReceived += outputAppender;
process.ErrorDataReceived += outputAppender;
process.StartInfo.FileName = #"C:\AppRunner.exe";
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.Arguments = #"""" + executablePath + #""" " + programArguments;
process.StartInfo.UserName = userName;
process.StartInfo.Domain = domain;
SecureString passwordString = new SecureString();
foreach (Char c in password)
{
passwordString.AppendChar(c);
}
process.StartInfo.Password = passwordString;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
exitCode = process.ExitCode;
output = outputString.ToString();
}
}
AppRunner basically does the same as the above fragment, but without username and password

Resources