I have written a windows service in C#. The requirement at the beginning was that I should be running only one instance of that service, but that has changed and now I need multiple instances. This is why I need to change the service name according to the configuration file.
What would be the best way to make it register with the correct name ? Should I write another tool to do it? Can I just read the name from App.config file and set it in the service and installer classes accordingly ?
PS> I do not really understand how that thing with names work - one should set names in service and installer classes, but then when installing with installutil.exe or even powershell new-service the name also should be specified. Does that have to be the same? Or one overrides another?
You can simply read it from the app.config and set it in the installer classes.
Normally, a class that inherits from Installer is automatically created. It contains a member of type System.ServiceProcess.ServiceInstaller, most likely named serviceProcessInstaller1. This has a property ServiceName you can set. Additionally, you need to set the ServiceName property of the ServiceBase derived class to the same value.
In a default implementation, these are set to constant values in the respective InitializeComponent methods, but there is no reason to stick with this. It can be done dynamically without problems.
I though I'd add my 2 cents since I ran into this. I have a file called "ProjectInstaller.cs" with designer and resources under it. Opening it up in design shows MyServiceInstaller and MyProjectInstaller as items on the design surface. I was able to change the names in the ProjectInstaller() constructor, and manually loaded the config file from the module directory:
public ProjectInstaller()
{
InitializeComponent();
var config = ConfigurationManager.OpenExeConfiguration(this.GetType().Assembly.Location);
if (config.AppSettings.Settings["ServiceName"] != null)
{
this.MyServiceInstaller.ServiceName = config.AppSettings.Settings["ServiceName"].Value;
}
if (config.AppSettings.Settings["DisplayName"] != null)
{
this.MyServiceInstaller.DisplayName = config.AppSettings.Settings["DisplayName"].Value;
}
}
In the same vein as Jason Goemaat's answer, this is how you would loop through all available installers in your project, which saves you the time of being sure you added each new service to this class. In my project, I have a total of 12 services (and we add a new one here and there), and we wanted them grouped together by the instance name, so SVC (Instance 1) Service XX and SVC (Instance 1) Service YY were next to each other when viewed in the services snap-in console.
public ProjectInstaller()
{
InitializeComponent();
var config = ConfigurationManager.OpenExeConfiguration(this.GetType().Assembly.Location);
string instanceName = config.AppSettings.Settings["Installer_NamedInstanceName"].Value;
string instanceID = config.AppSettings.Settings["Installer_NamedInstanceID"].Value;
bool usesNamedInstance = !string.IsNullOrWhiteSpace(instanceName) && !string.IsNullOrWhiteSpace(instanceID);
if (usesNamedInstance)
{
foreach (var installer in this.Installers)
{
if (installer is ServiceInstaller)
{
var ins = (ServiceInstaller)installer;
ins.ServiceName = ins.ServiceName + "-" + instanceID;
// Want the service to be named SVC (Instance Name) Audit Log Blah Blah Service
ins.DisplayName = ins.DisplayName.Replace("SVC ", "SVC (" + instanceName + ") ");
}
}
}
}
HOWEVER, there is something else that you need to do - When initializing the service, you must also change the service name, otherwise you'll get an error along the lines of "The executable doesn't implement the service". I did this by implementing the following code in Program.cs:
internal static void HandleCustomServiceName(ServiceBase sbase)
{
if (!string.IsNullOrWhiteSpace(customInstanceName))
{
sbase.ServiceName = sbase.ServiceName + "-" + customInstanceName;
}
}
Then, in the constructor of each service:
public SystemEventWatcher()
{
InitializeComponent();
Program.HandleCustomServiceName(this);
}
I've upvoted Jason's answer for paving the way to implementing this in my own project. Thanks!
Related
I am trying to filter for source control files that were either created or modified within a specific time period on particular Team Foundation Server 2015 branches. I am thus far able to access file properties (e.g. url) with the Microsoft.VisualStudio.Services.WebAPI and Microsoft.TeamFoundation.SourceControl.WebApi libraries with a C# .Net Framework 4.8 Console Application using the GitHttpClient class.
The GetItemsAsync() method of this class returns a list of "GitItems" that contain a "path" property that can be passed as an argument into the System.IO class FileInfo to instantiate an object with the properties I need: CreationTime and LastWriteTime. However, the GitItem objects do not include the full file (blob) path that FileInfo (as well as the class File) needs to generate these properties accurately. The path property only includes the file name (e.g. '/.gitignore'). Therefore, in the code below, the variable lastWriteTime and the CreationTime property both return '12/31/1600 7:00:00 PM,' since the path isn't recognized.
static void Main(string[] args)
{
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
VssConnection connection = new VssConnection(new Uri(teamCollection), creds);
// Get a GitHttpClient to talk to the Git endpoints
GitHttpClient gitClient = connection.GetClient<GitHttpClient>();
// Get data about a specific repository
var repositories = gitClient.GetRepositoriesAsync(teamProject).Result;
GitVersionDescriptor descriptor = new GitVersionDescriptor()
{
VersionType = GitVersionType.Branch,
Version = "develop",
VersionOptions = GitVersionOptions.None
};
foreach (var repository in repositories)
{
var branches = gitClient.GetBranchesAsync(repository.Id).Result;
var items = gitClient.GetItemsAsync(repository.Id, recursionLevel: VersionControlRecursionType.Full, versionDescriptor: descriptor, includeContentMetadata: true).Result;
foreach (var item in items)
{
var fullPath = Path.GetFullPath(item.Path);
FileInfo file = new FileInfo(fullPath);
DateTime lastWriteTime = file.LastWriteTime;
}
Console.WriteLine(repository.Name);
}
}
}
}
According to your code, you are using GitHttpClient.GetItemsAsync method.
public Task<GitItemsCollection> GetItemsAsync(
Guid repositoryId,
string path,
GitVersionDescriptor version,
VersionControlRecursionType recursionLevel,
bool includeContentMetadata,
bool includeLatestChange,
Object userState
)
This will return a server side git path. File info class with LastWriteTime properties
Gets or sets the time when the current file or directory was last written to. This should be a local system path.
That's why the path isn't recognized. Which may return a date kind of '12/31/1600 7:00:00 PM,'
Your question is similar to this VSTS API - repository creation date
Don't think it is possible to get the exact date of the moment the
operation create repo was completed. However, logically the birthday
of the repository is usually considered its first commit date.
If that's what you're looking for, you can achieve your goal with a
usual Git command:
git log -1 --reverse --format="format:%ci"
Besides, you could also get a git commit with detail info through Rest API. Also take a look at this blog, which maybe helpful.
my project has been an extremely fun journey so far but I am looking to save the configuration of the Server settings that it will connect (using MySQL Net/Connector).
When the application has loaded up, by default it connects to a server named 'sqlserver05' but I want the user/admin to be able to configure the server settings in a menustrip. So I navigate to the menustrip and you can click 'Configure' where another form pops up asking for server details.
I can do this just by a global string but I have to change the settings everytime the application runs. Can I not create an XML file to read the configuration settings that I just changed?
Sorry if I am not being clear. Many thanks,
Brandon
Yes, you can. An easy way to do this is to use application settings. This is an out-of-the-box implementation of (user and program) settings that is serialized to XML.
Please take a look at the ancient, but still applicable Using Settings in C#.
Effectively what you have to do:
Add a settings file to your project. Go the the Solution Explorer, right click on your project and select Properties. Then select Settings. Follow the steps there.
Create a setting. (In the following code it has the name PropertyName)
Get and set that setting in code.
string value = Properties.Settings.PropertyName; // get
Properties.Settings.Default.PropertyName = value; // set
Save the settings when you have changed anything:
Properties.Settings.Default.Save()
I think in your case it's better to use the Settings class that came with C#, take a look at these links.
1 , 2
First of all, create a simple POCO object to handle the value you wish to set, then read / write this object through a serializer.
You could use a Javascript serializer to generate a JSON file (which is more "trendy" than XML, but if you prefer XML, the mechanism remains the same) :
class DatabaseSettings
{
// Settings file path
private const string DEFAULT_FILENAME = "settings.json";
// Server name or IP
public string Server { get; set; } = "127.0.0.1";
// Port
public int Port { get; set; } = 3306;
// Login
public string Login { get; set; } = "root";
// Password
public string Password { get; set; }
public void Save(string fileName = DEFAULT_FILENAME)
{
File.WriteAllText(
fileName,
(new JavaScriptSerializer()).Serialize(this));
}
public static DatabaseSettings Load(string fileName = DEFAULT_FILENAME)
{
var settings = new DatabaseSettings();
if (File.Exists(fileName))
settings = (new JavaScriptSerializer()).Deserialize<DatabaseSettings>(File.ReadAllText(fileName));
return settings;
}
}
Usage is then the following :
// Read
var settings = DatabaseSettings.Load(/* your path */);
// update
settings.Server = "10.10.10.2";
// save
settings.Save(/* your path */);
I need to develop a Shell Context Menu extension that references some other custom assemblies... I don't want to assign a Strong Name Key to those custom assemblies!
The guide I followed to do this uses the SharpShell project and illustrates how to sign (but does not expalins why) the assembly... and this is my problem: if I sign my final .dll then I have many errors during my project's building phase, because some assemblies my project references are not strongly named ("Referenced assembly does not have a strong name").
In general, googling about the C# Shell Extension implementation, all best tutorials I found sign the final assembly... is it mandatory?
Without signing the assembly ServerManager.exe returns this error: "The file 'XYZ.dll' is not a SharpShell Server".
Finally I've solved my troubles... the SharpShell.dll file obtained through NuGet was a different version of the ServerManager.exe ones.
Uninstalling the SharpShell NuGet package and directly referencing the SharpShell.dll you find inside the ServerManager folder was my solution!
Moreover, I was looking between the article comments... please read this question.
You don't need to use old DLL.
Please use this code directly, without using ServerManager.exe.
private static ServerEntry serverEntry = null;
public static ServerEntry SelectedServerEntry
{
get
{
if (serverEntry == null)
serverEntry = ServerManagerApi.LoadServer("xxx.dll");
return serverEntry;
}
}
public static ServerEntry LoadServer(string path)
{
try
{
// Create a server entry for the server.
var serverEntry = new ServerEntry();
// Set the data.
serverEntry.ServerName = Path.GetFileNameWithoutExtension(path);
serverEntry.ServerPath = path;
// Create an assembly catalog for the assembly and a container from it.
var catalog = new AssemblyCatalog(Path.GetFullPath(path));
var container = new CompositionContainer(catalog);
// Get the exported server.
var server = container.GetExport<ISharpShellServer>().Value;
serverEntry.ServerType = server.ServerType;
serverEntry.ClassId = server.GetType().GUID;
serverEntry.Server = server;
return serverEntry;
}
catch (Exception)
{
// It's almost certainly not a COM server.
MessageBox.Show("The file '" + Path.GetFileName(path) + "' is not a SharpShell Server.", "Warning");
return null;
}
}
Install code:
ServerRegistrationManager.InstallServer(SelectedServerEntry.Server, RegistrationType.OS64Bit, true);
Register code:
ServerRegistrationManager.RegisterServer(SelectedServerEntry.Server, RegistrationType.OS64Bit);
I have a problem with updating dynamic Web Reference using WSDL.exe tool.
When I'm using "Update Web Reference" in VS, everything is working as expected.
Below is generated code (part of Reference.cs file):
public MyService() {
this.Url = global::ServerReference.Properties.Settings.Default.ServerReference_Reference_MyService;
if ((this.IsLocalFileSystemWebService(this.Url) == true)) {
this.UseDefaultCredentials = true;
this.useDefaultCredentialsSetExplicitly = false;
}
else {
this.useDefaultCredentialsSetExplicitly = true;
}
}
I'm getting necessary information from application properties which are then stored in config file and therefore can be changed without rebuilding application.
However when I use following command:
.\tools\wsdl.exe /l:cs /n:ServerReference /o".\ServerReference\Web References\Reference\Reference.cs" http://localhost:52956/MyService/MyService.asmx
it is created with fixed URL address in Reference.cs file.
Does anybody know how I should change my command to achieve the same Reference.cs file as in Visual Studio?
I don't think you can generate the same code with wsdl.exe.
But if the main thing you want to achieve is generating code that takes the service address from app.config then you can use wsdl.exe with the "/appsettingurlkey" switch.
The code you'll get will be something like this:
public WebService1() {
string urlSetting = System.Configuration.ConfigurationManager.AppSettings["ConfigKeyForServiceUrl"];
if ((urlSetting != null)) {
this.Url = urlSetting;
}
else {
this.Url = "http://localhost:65304/WebService1.asmx";
}
}
Be aware that it reads from 'appSettings' not from 'applicationSettings' through the Settings class, so you'll have to modify your app.config. And it doesn't contain the 'UseDefaultCredentials' stuff either.
I have a situation where there are lots of test cases that don't belong to a test folder. This is fine, but I'd like to write an application to move these 'orphaned' test cases into a test folder (mostly so it's easy to easily see how the tests are doing)
All of the test cases and the test folder I create are in the same project, but I get the following errors;
Validation error: TestFolder.TestCases is an invalid relationship. One or more of the artifacts is in a different project.
Validation error: TestCase.TestFolder is an invalid relationship. One or more of the artifacts is in a different project.
These seem to be telling me that I am assigning the test cases to a test folder in a different project - but they aren't.
Here's a snip of the code - m_currentRallyProject and m_workspace have already been set by a different method
Any thoughts?
public void CreateTestFolderForOrphanedTestCases(HierarchicalRequirement aUserStory, List<TestCase> testCases)
{
TestFolder myNewTestFolder = createTestFolder(aUserStory.Name);
for (int i = 0; i < testCases.Count; i++)
{
TestCase myTestCase = (TestCase)testCases[i];
myTestCase.TestFolder = myNewTestFolder;
OperationResult myResult = m_rallyService.update(myTestCase);
if (hasErrors(myResult))
{
updateStatus("Could not set Test Folder for " + myTestCase.FormattedID);
printWarningsErrors(myResult);
}
else
{
updateStatus("updated test case " + myTestCase.FormattedID);
}
}
}
private TestFolder createTestFolder(String testFolderName, TestFolder aParentTestFolder = null)
{
TestFolder myNewTestFolder = new TestFolder();
myNewTestFolder.Name = testFolderName;
myNewTestFolder.Project = m_currentRallyProject;
myNewTestFolder.Workspace = m_workspace;
CreateResult createTestFolderResult = m_rallyService.create(myNewTestFolder);
if (hasErrors(createTestFolderResult))
{
// something went wrong
Console.WriteLine("Could not create Test Folder");
printWarningsErrors(createTestFolderResult);
}
else
{
myNewTestFolder = (TestFolder)m_rallyService.read(createTestFolderResult.Object);
return myNewTestFolder;
}
return null;
}
Dropping an answer in from the comments above :)
Make certain they're in the same project - you shouldn't get this message if they are. Being in the same Project Hierarchy doesn't count. I.E. a Test Folder that is in a Child Project of the current Project, even with child scoping down = true, counts as being in a different Project. Try adding some logging that outputs the Project Name or ref for both the Test Case and the Target Test Folder.
If you add some logging that outputs Project metadata for both TestCase and target TestFolder, make sure to output both Name and ref - since Project Name is not guaranteed to be unique (different Rally Projects can have the same Name).