I've read several posts here about replacing the obsolete TeamFoundationServer with TfsTeamProjectCollectionFactory but not in a web service. The obsolete method works in my web service but the TfsTeamProjectCollectionFactory method does not. It always throws an exception setting the workspace variable:
There is no working folder mapping for C:\MyPath
The web service uses an app pool assigned to my identity which is a local machine admin and the new method works if I make it a console app.
Is it doable to replace TeamFoundationServer in a web service?
void TfsCheckout(string tfsServer, string wkSpace, string fileName)
{
try
{
//new method
var pc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(tfsServer));
var versionControlServer = (VersionControlServer)pc.GetService((typeof(VersionControlServer)));
var workspace = versionControlServer.GetWorkspace(wkSpace);
var result = workspace.PendEdit(fileName);
//obsolete method
var tfs = new TeamFoundationServer(tfsServer, new UICredentialsProvider());
var versionControl = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
var workspc = versionControl.GetWorkspace(Environment.MachineName, versionControl.AuthenticatedUser);
result = workspc.PendEdit(fileName);
UpdateStatus(new UpdateStatusEventArgs("Checkout: " + fileName));
UpdateStatus(new UpdateStatusEventArgs("Result: " + result + " (1=success 0=fail)"));
}
catch (Exception ex)
{
UpdateStatus(new UpdateStatusEventArgs(ex.ToString()));
UpdateStatus(new UpdateStatusEventArgs("Done"));
}
}
Related
I am writing a program in C# (dotnet 6/Mac) which will issue a PowerShell command to a remote EC2 instance running Windows (2012/PowerShell version 5.x) to download a file from S3.
I am on a Mac and I am able to connect to the EC2 Instance with PowerShell for Mac.
Here is the C# program:
public void DownloadS3FileToRemoteMachine(string host,
string user,
string password,
string bucket,
string s3path,
string localPath)
{
string s3DownloadCommand =
$"aws s3 cp s3://{bucket}{s3path} {localPath}";
var securePass = new SecureString();
foreach (char p in password)
{
securePass.AppendChar(p);
}
var credential = new PSCredential(user, securePass);
var connectionInfo = new WSManConnectionInfo
{
ComputerName = host,
Credential = credential,
NoEncryption = true,
Scheme = WSManConnectionInfo.HttpScheme
};
using Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo);
using PowerShell? ps = PowerShell.Create(rs).AddScript(s3DownloadCommand);
Collection<PSObject>? results;
try
{
rs.Open();
results = ps.Invoke();
if (ps.HadErrors)
{
string errors = string.Join(Environment.NewLine, ps.Streams
.Error
.ReadAll()
.Select(err => err.ErrorDetails.Message));
_logger.LogError("Error while downloading the file from S3 to local path {LocalPath}, " +
"error {ErrorMsg}", localPath, errors);
}
}
catch (Exception e)
{
_logger.LogError(e, "Error copying the file from S3 to remote machine");
throw;
}
string enumerable = string.Join("\n", results.Select(r => r.ToString()));
_logger.LogInformation(enumerable);
}
With this, I get the error:
Connecting to remote server 10.62.166.198 failed with the following error message : Authorization failed For more information, see the about_Remote_Troubleshooting Help topic.
However, I know that the code works because I have access to another Windows machine running Window 10 on my local network and I am able to successfully download the file on that machine.
If I remove the line NoEncryption = true from the configuration then I get a different message:
Connecting to remote server 10.62.166.198 failed with the following error message : MI_RESULT_FAILED For more information, see the about_Remote_Troubleshooting Help topic.
Any help will be tremendously appreciated.
Thanks to the amazing people on the PowerShell Discord channel (access via https://www.reddit.com/r/PowerShell) I learnt that the current functionality of PowerShell to remote from Linux to Windows is very limited.
I had to choose OpenSSH option i.e. install OpenSSH on Windows machine, thereafter, I was able to access the service via C# like so:
var info = new ConnectionInfo(host, user, new PasswordAuthenticationMethod(user, password));
using var client = new SshClient(info);
client.HostKeyReceived += (_, e) => e.CanTrust = true;
client.Connect();
string s3DownloadCommand = $"aws s3 cp s3://{bucket}/{source} {destination}";
using SshCommand? cmd = client.CreateCommand(s3DownloadCommand);
string? output = cmd.Execute();
I've created an application that gets all of my Sites in IIS and checking each URL in Bindings if there's an HTTP error and if a certain error is encountered, my application will reset the IIS Site Instance in IIS, however, it's not enough to fix the error as it should. I would be needing to reset both the Site and the Application pool it belongs to.
Is there any way to get the Application Pool object based on the Site object?
I have tried the code below but this only works if an application pool only has 1 Site/Application in it. The problem is, I don't get the matching App Pool of the Site if I have a number mismatch between the List of Sites vs List of ApplicationPools since 1 AppPool can have multiple Sites/Applications.
ServerManager serverMgr = new ServerManager();
SiteCollection sites;
ApplicationPoolCollection appPools;
public List<(Site, ApplicationPool, string)> getSiteInfo()
{
List<(Site, ApplicationPool, string)> siteInfo = new List<(Site, ApplicationPool, string)>();
List<string> siteUrls = new List<string>();
sites = serverMgr.Sites;
appPools = serverMgr.ApplicationPools;
foreach (Site site in sites)
{
foreach (ApplicationPool appPool in appPools)
{
foreach (Binding binding in site.Bindings)//getting site url
{
string bindingInfo = binding.BindingInformation; // "192.111.1.1:80:google.com" /// *:808:
string[] adrs = bindingInfo.Split(':'); //0 = ip, 1 = port, 2 = hostname
if (adrs[0] == "*")
{
adrs[0] = "localhost";
}
//adding to my list of sites and it's corresponding Application Pool in 1 tuple variable
siteInfo.Add((site, appPool, adrs[0] + ":" + adrs[1] + "/" + adrs[2])); //localhost:80/google.com
}
}
}
return siteInfo;
}
I need something similar to this code: (see comments)
public List<(Site, ApplicationPool, string)> getSiteInfo()
{
List<(Site, ApplicationPool, string)> siteInfo = new List<(Site, ApplicationPool, string)>();
List<string> siteUrls = new List<string>();
sites = serverMgr.Sites;
foreach (Site site in sites)
{
foreach (Binding binding in site.Bindings)
{
//I need something like this to make sure the AppPool I'm getting is of the Site I have.
ApplicationPool appPool = site.ApplicationPoolName;//<-- This Line
string bindingInfo = binding.BindingInformation;
string[] adrs = bindingInfo.Split(':');
if (adrs[0] == "*")
{
adrs[0] = "localhost";
}
//So that I can do this when passing the Site Info tuple Variable with the Site Object together with the corresponding AppPool for later use of the IISReset Class in my project.
siteInfo.Add((site, appPool, adrs[0] + ":" + adrs[1] + "/" + adrs[2]));
}
}
return siteInfo;
}
Sorry for the long and sloppy explanation but I would be happy to clear out if you have questions with this. Thank you.
I figured it out by using Applications attribute of a site. To get the application pool of a Site I did the code below. Where I got the list of applications of a site and used that to identify the app pool from the list of application pools in the server. However this only works if the site only has 1 application, that's why I'm indexing 0(zero) to get the first application (which is the only application of my site) to search for it's corresponding app pool.
ApplicationCollection apps = site.Applications;
ApplicationPool appPool = serverMgr.ApplicationPools[appname[0].ApplicationPoolName];
When using
var properties = new NameValueCollection();
properties["quartz.plugin.triggHistory.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin";
properties["quartz.plugin.jobInitializer.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin";
properties["quartz.plugin.jobInitializer.fileNames"] = "quartz_jobs.xml";
properties["quartz.plugin.jobInitializer.failOnFileNotFound"] = "true";
properties["quartz.plugin.jobInitializer.scanInterval"] = "120";
// First we must get a reference to a scheduler
_schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = _schedulerFactory.GetScheduler();
The windows service / quartz cannot resolve the path of quartz_jobs.xml.
If i run this as console it works fine.
public static void StartJobs()
{
try
{
_logger = LogManager.GetCurrentClassLogger();
var properties = new NameValueCollection();
properties["quartz.plugin.triggHistory.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin";
properties["quartz.plugin.jobInitializer.type"] = "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin";
properties["quartz.plugin.jobInitializer.fileNames"] = "quartz_jobs.xml";
properties["quartz.plugin.jobInitializer.failOnFileNotFound"] = "true";
properties["quartz.plugin.jobInitializer.scanInterval"] = "120";
// First we must get a reference to a scheduler
_schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = _schedulerFactory.GetScheduler();
// start the schedule
_scheduler.Start();
}
catch (Exception ex)
{
_logger.Error(ex);
throw new Exception(ex.Message);
}
}
If it's still not working, include the file as an embedded resource in the project, set the action to Copy always, to be sure. Then provide the full file path to the quartz property:
Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "quartz_jobs.xml")
I know this thread is from 2015, but i can't find any information regarding using quartz.net in a windows service. In my case, I am using .Net Core 2.1 Generic Host as a windows service with the quartz_jobs.xml which is referenced in my appsettings.json file. When the windows service starts up an looks for the quartz_job.xml it tries to find it in c:\windows\system32. But my quartz_job.xml is located where my executable is located. I tracked down Method ResolveFile in Quaztz\Util\FileUtil.cs of their repo where is says to put a "~" to for relative file. So I changed my appsettings.json to
"plugin": {
"jobInitializer": {
"type": "Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins",
"fileNames": "~\\quartz_jobs.xml"
}
and now the windows service is able to read the quartz_jobs.xml. I would expect the if you change
properties["quartz.plugin.jobInitializer.fileNames"] = "quartz_jobs.xml";
to
properties["quartz.plugin.jobInitializer.fileNames"] = "~\\quartz_jobs.xml";
it should also work.
public void CreateVirtualDirectory(string nameDirectory, string realPath)
{
System.DirectoryServices.DirectoryEntry oDE;
System.DirectoryServices.DirectoryEntries oDC;
System.DirectoryServices.DirectoryEntry oVirDir;
try
{
oDE = new DirectoryEntry("IIS://" + this._serverName + "/W3SVC/1/Root");
//Get Default Web Site
oDC = oDE.Children;
//Add row
oVirDir = oDC.Add(nameDirectory, oDE.SchemaClassName.ToString());
//Commit changes for Schema class File
oVirDir.CommitChanges();
//Create physical path if it does not exists
if (!Directory.Exists(realPath))
{
Directory.CreateDirectory(realPath);
}
//Set virtual directory to physical path
oVirDir.Properties["Path"].Value = realPath;
//Set read access
oVirDir.Properties["AccessRead"][0] = true;
//Create Application for IIS Application (as for ASP.NET)
oVirDir.Invoke("AppCreate", true);
oVirDir.Properties["AppFriendlyName"][0] = nameDirectory;
//Save all the changes
oVirDir.CommitChanges();
}
catch (Exception ex)
{
throw ex;
}
}
This above function work fine _serverName = "localhost" but this always create virtual directory in Default Web Site in IIS. While I have another sample site created with name MySite on localhost:8080. so when I put _serverName = "localhost:8080" it gives me error.
This line:
oDE = new DirectoryEntry("IIS://" + this._serverName + "/W3SVC/1/Root");
Always assumes the default web site. The "1" is the ID of the website. Replace the "1" with the ID of the site you want to create the virtual directory in. You can find the site ID in the IIS here:
You can, if you desire, enumerate all of the websites programmatically using Directory Services as well to help you find the right ID:
DirectoryEntry w3svc = new DirectoryEntry("IIS://" + this._serverName + "/w3svc");
foreach(DirectoryEntry de in w3svc.Children)
{
if(de.SchemaClassName == "IIsWebServer")
{
var id = de.Name; //Name is the ID
var displayName = de.Properties["ServerComment"].Value.ToString();
}
}
Each WebSite has a different Id - the LDAP address of the "MySite" is probably something like this:
IIS://" + this._serverName + "/W3SVC/**2**/Root
I have successfully automated the process of creating a new IIS website, however the code I've written doesn't care about application pools, it just gets added to DefaultAppPool. However I'd like to add this newly created site to an existing application pool.
Here is the code I'm using to create the new website.
var w3Svc = new DirectoryEntry(string.Format("IIS://{0}/w3svc", webserver));
var newsite = new object[] { serverComment, new object[] { serverBindings }, homeDirectory };
var websiteId = w3Svc.Invoke("CreateNewSite", newsite);
site.Invoke("Start", null);
site.CommitChanges();
<update>
Although this is not directly related to the question, here are some sample values being used above. This might help someone understand exactly what the code above is doing more easily.
webServer: "localhost"
serverComment: "testing.dev"
serverBindings: ":80:testing.dev"
homeDirectory: "c:\inetpub\wwwroot\testing\"
</update>
If I know the name of the application pool that I'd like this web site to be in, how can I find it and add this site to it?
You have to assign the AppPool on the virtual dir (not the webserver) and set the AppIsolated property to 2 which mean pooled-process ;)
http://msdn.microsoft.com/en-us/library/ms525598%28v=VS.90%29.aspx
Relevant code sample from link:
static void AssignVDirToAppPool(string metabasePath, string appPoolName)
{
// metabasePath is of the form "IIS://<servername>/W3SVC/<siteID>/Root[/<vDir>]"
// for example "IIS://localhost/W3SVC/1/Root/MyVDir"
// appPoolName is of the form "<name>", for example, "MyAppPool"
Console.WriteLine("\nAssigning application {0} to the application pool named {1}:", metabasePath, appPoolName);
try
{
DirectoryEntry vDir = new DirectoryEntry(metabasePath);
string className = vDir.SchemaClassName.ToString();
if (className.EndsWith("VirtualDir"))
{
object[] param = { 0, appPoolName, true };
vDir.Invoke("AppCreate3", param);
vDir.Properties["AppIsolated"][0] = "2";
Console.WriteLine(" Done.");
}
else
Console.WriteLine(" Failed in AssignVDirToAppPool; only virtual directories can be assigned to application pools");
}
catch (Exception ex)
{
Console.WriteLine("Failed in AssignVDirToAppPool with the following exception: \n{0}", ex.Message);
}
}
Note that if you are not explicitly adding a new virtual directory to the application, the metabasePath will simply be "IIS://<servername>/W3SVC/<siteID>/Root"
You need to get the AppPoolfrom IIS://{0}/W3SVC/AppPools, and attach it to the site's AppPoolId. Something like:
var appPool = new DirectoryEntry(
string.Format("IIS://{0}/W3SVC/AppPools/{1}", webServer, appPoolName)
);
site.Properties["AppPoolId"].Value = appPool;
site.Properties["AppPoolId"][0]= "poolname";