How to unit test Azure Functions in Visual Studio - visual-studio

I am using Visual Studio to create Azure Functions. I can create, publish and run functions manually. If I set my Function project to Start Up and run, I host starts. How do I get the Host to start when using MSTest ?
I want to write a test using RestSharp and invoke the functions during the tests - the way the actual applicaion will work. It seems I need a way to get MSTest to start the Azure Function Host.

I was hoping to find an approach similar to debugging Asp.Net/SOAP in older versions of VS where the MSTest engine would start the IISExpress and attach VS to the Asp.Net projects. Edit and continue was supported.
I have worked out the following approaches, so far:
RestSharp Code:
var url = $"http://localhost:7071/api";
var functionKey = "this value is ignored by 'Azure Functions Core Tools' ";
var client = new RestSharp.RestClient(url);
var request = new RestSharp.RestRequest("GetConnectionString", RestSharp.Method.POST);
request.AddHeader("x-functions-key", functionKey);
var response = client.Execute<string>(request);
Option 1:
Run 2nd instance of VS and run the Functions.
Update code to reflect url paths displayed by Azure Functions Core Tools.
Edit and continue works.
Option 2:
Add this class to UnitTest project:
Attach to func.exe process to debug functions
Edit and continue do not work.
[TestClass]
public class Initializer
{
static System.Diagnostics.Process process { get; set; }
[AssemblyInitialize]
public static void Initialize(TestContext context)
{
//process does not use the WorkingDirectory properly with %userprofile%
var userprofile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var path = $#"{userprofile}\source\repos\mypro\...\myproj.Functions\bin\Debug\netcoreapp3.1";
var startInfo = new ProcessStartInfo
{
FileName = #"cmd.exe",
Arguments = #"/C ""%localappdata%\AzureFunctionsTools\Releases\3.2.0\cli_x64\func.exe host start --port 7071 --pause-on-error""",
//RedirectStandardInput = true,
//RedirectStandardOutput = true,
//RedirectStandardError = true,
//UseShellExecute = false,
UseShellExecute = true,
WorkingDirectory = path
};
process = new System.Diagnostics.Process()
{
StartInfo = startInfo
};
process.Start();
// Thread.Sleep(3000);
}
[AssemblyCleanup]
public static void Cleanup()
{
process.CloseMainWindow();
}
}
Option 3:
Start func.exe process
Working Folder: ...\source\repos\xxx\xxx\xxx.Functions\bin\Debug\netcoreapp3.1\
Command Line:
"C:\Users\username\AppData\Local\AzureFunctionsTools\Releases\3.2.0\cli_x64\func.exe" host start --port 7071 --pause-on-error
Attach to func.exe process.
Edit and continue does not work.

Related

Programmatically access TFS annotations to determine owner

I'm working on a project team and our application is in TFS. I'm attempting to determine how many lines of code each team member is responsible. In TFS, I'm aware of the Annotate feature in the Visual Studio interface which allows you to see who last modified each line of code so I know TFS has this information.
I've written a small console app which accesses my TFS project and all its files, but I now need to programmatically access annotations so I can see who the owner of each line is. Here is my existing code:
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
public class Program
{
static void Main(string[] args)
{
var credentials = new NetworkCredential(username, password, domain);
var server = new TfsTeamProjectCollection(new Uri(serverUrl), credentials);
var version = server.GetService(typeof(VersionControlServer)) as VersionControlServer;
var items = version.GetItems(projectPath, RecursionType.Full);
var fileItems = items.Items.Where(x => x.ItemType == ItemType.File);
foreach (var fileItem in fileItems)
{
var serverItem = fileItem.ServerItem;
//TODO: retrieve and parse annotations
}
}
}
I can't seem to figure out how to retrieve annotations once I have the TFS item. This link explains how to do it by calling TFPT, but after implementing it (tfpt annotate /noprompt <filename>), you are only give the last changeset and code per line, not the owner.
I also found a Microsoft.TeamFoundation.VersionControl.Server namespace that has an Annotation class. I installed TFS on my machine to have access to that DLL, but it doesn't seem like it is of any help to this problem.
How can you programmatically access TFS annotations to determine the owner of a line of code for a file?
You may have to query the branch when a Item's change type is Branch.
For a simple example, there is a scenario
$/Project
/Main`
/a.txt
/Develop
/a.txt (branched from main)
When you query the history of $/project/Develop/a.txt, you can also get the history of $/project/Main/a.txt using following code
void GetAllHistory(string serverItem)
{
var changesets=vcs.QueryHistory(serverItem,
Microsoft.TeamFoundation.VersionControl.Client.VersionSpec.Latest,
0,
Microsoft.TeamFoundation.VersionControl.Client.RecursionType.None,
null,
new Microsoft.TeamFoundation.VersionControl.Client.ChangesetVersionSpec(1),
Microsoft.TeamFoundation.VersionControl.Client.VersionSpec.Latest,
int.MaxValue,
true,
false);
foreach (var obj in changesets)
{
Microsoft.TeamFoundation.VersionControl.Client.Changeset cs = obj as Microsoft.TeamFoundation.VersionControl.Client.Changeset;
if (cs == null)
{
return;
}
foreach (var change in cs.Changes)
{
if (change.Item.ServerItem != serverItem)
{
return;
}
Console.WriteLine(string.Format("ChangeSetID:{0}\tFile:{1}\tChangeType:{2}", cs.ChangesetId,change.Item.ServerItem, change.ChangeType));
if ((change.ChangeType & Microsoft.TeamFoundation.VersionControl.Client.ChangeType.Branch) == Microsoft.TeamFoundation.VersionControl.Client.ChangeType.Branch)
{
var items=vcs.GetBranchHistory(new Microsoft.TeamFoundation.VersionControl.Client.ItemSpec[]{new Microsoft.TeamFoundation.VersionControl.Client.ItemSpec(serverItem, Microsoft.TeamFoundation.VersionControl.Client.RecursionType.None)},
Microsoft.TeamFoundation.VersionControl.Client.VersionSpec.Latest);
GetAllHistory(items[0][0].Relative.BranchToItem.ServerItem);
}
}
}
}

Get latest from Visual Studio Team Services using Command Line passing /login credentials with TF.exe

Has anyone had success getting latest source code from the Visual Studio Team Services (formerly Visual Studio Online, Team Foundation Service) Version Control Server using the command line and passing in credentials programmatically?
-I have discovered that you can't use the Windows ID credentials that you use to login to Team Explorer or the VSO website in the command line. You need to create Alternate Credentials for the user profile in Team Services.
-I have found out that if you omit the /login in tf.exe, the Team Services login dialog appears and asks you to type in your Windows ID credentials (unless they are already cached in your Team Explorer or Visual Studio (or even possibly Browser and Windows Credential Caches)
-I have found out that the alternate credential work Using the Java version of tf.exe - Team Explorer Everywhere Command Line Client (TEE CLC). TEE CLC actually uses the /login credentials that you pass in and lets you connect. The same thing does NOT seem to be possible with the TF.EXE in C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ BUT INSTALLING JAVA ON THIS BUILD ENVIRONMENT IS AGAINST POLICY. So the TEE CLC is NOT a viable option.
tf get $/MyProj /collection:https://myaccount.visualstudio.com/DefaultCollection /login:user:pass
the above command simply ignores the /login credentials if you have the Windows ID credentials cached or it returns the error message TF30063: You are not authorized to access myaccount.visualstudio.com (which is not true, because the credentials DO work with the Java client)
Are there any other alternatives that do not require installing Java?
I got an answer from Microsoft Support: AA Creds for VSO do not work with TF.EXE at this time. TEE CLC or using object model code are the only alternatives currently. We are looking at doing this in the future.
Object Model Code refers to the Microsoft.TeamFoundation.VersionControl.Client Namespace in the dll by the same name. I ended up writing a quick C# console app to download the latest code without installing Java. An added benefit of this approach is that it does not require creating a throwaway Workspace.
if you use the code below to create an executable called tfsget.exe it can be called from the command line like this:
tfsget https://myvso.visualstudio.com/DefaultCollection $/MyProj/Folder c:\Projects login password
and I added a silent switch to suppress listing each file that can be used like:
tfsget https://myvso.visualstudio.com/DefaultCollection $/MyProj/Folder c:\Projects login password silent
here's the code, hope this helps until MS updates TF.exe
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace TfsGet
{
class Program
{
static void Main(string[] args)
{
var tfsParams = TfsDownloadParams.Create(args);
var tpc = new TfsTeamProjectCollection(new Uri(tfsParams.ServerUrl), tfsParams.Credentials);
CheckAccess(tpc, tfsParams);
Download(tpc, tfsParams);
}
private static void CheckAccess(TfsTeamProjectCollection tpc, TfsDownloadParams tfsParams)
{
try
{
tpc.Authenticate();
}
catch
{
Console.WriteLine("TFS Authentication Failed");
Console.WriteLine("Server Url:{0}", tfsParams.ServerUrl);
Console.WriteLine("Project Path:{0}", tfsParams.ServerProjectPath);
Console.WriteLine("Target Path:{0}", tfsParams.TargetPath);
Environment.Exit(1);
}
}
static void Download(TfsTeamProjectCollection tpc, TfsDownloadParams tfsParams)
{
var versionControl = tpc.GetService<VersionControlServer>();
// Listen for the Source Control events.
versionControl.NonFatalError += Program.OnNonFatalError;
var files = versionControl.GetItems(tfsParams.ServerProjectPath, VersionSpec.Latest, RecursionType.Full);
foreach (Item item in files.Items)
{
var localFilePath = GetLocalFilePath(tfsParams, item);
switch (item.ItemType)
{
case ItemType.Any:
throw new ArgumentOutOfRangeException("ItemType.Any - not sure what to do with this");
case ItemType.File:
if (!tfsParams.Silent) Console.WriteLine("Getting: '{0}'", localFilePath);
item.DownloadFile(localFilePath);
break;
case ItemType.Folder:
if (!tfsParams.Silent) Console.WriteLine("Creating Directory: {0}", localFilePath);
Directory.CreateDirectory(localFilePath);
break;
}
}
}
private static string GetLocalFilePath(TfsDownloadParams tfsParams, Item item)
{
var projectPath = tfsParams.ServerProjectPath;
var pathExcludingLastFolder = projectPath.Substring(0, projectPath.LastIndexOf('/')+1);
string relativePath = item.ServerItem.Replace(pathExcludingLastFolder, "");
var localFilePath = Path.Combine(tfsParams.TargetPath, relativePath);
return localFilePath;
}
internal static void OnNonFatalError(Object sender, ExceptionEventArgs e)
{
var message = e.Exception != null ? e.Exception.Message : e.Failure.Message;
Console.Error.WriteLine("Exception: " + message);
}
}
public class TfsDownloadParams
{
public string ServerUrl { get; set; }
public string ServerProjectPath { get; set; }
public string TargetPath { get; set; }
public TfsClientCredentials Credentials { get; set; }
public bool Silent { get; set; }
public static TfsDownloadParams Create(IList<string> args)
{
if (args.Count < 5)
{
Console.WriteLine("Please supply 5 or 6 parameters: tfsServerUrl serverProjectPath targetPath userName password [silent]");
Console.WriteLine("The optional 6th 'silent' parameter will suppress listing each file downloaded");
Console.WriteLine(#"Ex: tfsget ""https://myvso.visualstudio.com/DefaultCollection"" ""$/MyProject/ProjectSubfolder"" ""c:\Projects Folder"", user, password ");
Environment.Exit(1);
}
var tfsServerUrl = args[0]; //"https://myvso.visualstudio.com/DefaultCollection";
var serverProjectPath = args[1]; // "$/MyProject/Folder Path";
var targetPath = args[2]; // #"c:\Projects\";
var userName = args[3]; //"login";
var password = args[4]; //"passsword";
var silentFlag = args.Count >= 6 && (args[5].ToLower() == "silent"); //"silent";
var tfsCredentials = GetTfsCredentials(userName, password);
var tfsParams = new TfsDownloadParams
{
ServerUrl = tfsServerUrl,
ServerProjectPath = serverProjectPath,
TargetPath = targetPath,
Credentials = tfsCredentials,
Silent = silentFlag,
};
return tfsParams;
}
private static TfsClientCredentials GetTfsCredentials(string userName, string password)
{
var networkCreds= new NetworkCredential(userName, password);
var basicCreds = new BasicAuthCredential(networkCreds);
var tfsCreds = new TfsClientCredentials(basicCreds)
{
AllowInteractive = false
};
return tfsCreds;
}
}
}
Here is a snippet using .NET TFS libraries & Powershell integration that worked for us - tf.exe does not play well with VSO authorization. Curious if anyone else has success using this route. We are using ADFS, so the powershell process is run as the user we want to authenticate with.
TF Get Latest Workspace.ps1
$path = "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\ReferenceAssemblies\v2.0"
Add-Type -Path "$path\Microsoft.TeamFoundation.Client.dll"
Add-Type -Path "$path\Microsoft.TeamFoundation.VersionControl.Client.dll"
Add-Type -Path "$path\Microsoft.TeamFoundation.VersionControl.Common.dll"
$collection = "https://mycorp.visualstudio.com/defaultcollection"
$tpc = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($collection)
$vc = $tpc.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])
# retrieve workspace by path
$projectPath = "$/MyApp/MyBranch"
$workspacePath = "C:\Projects\MyApp\MyBranch"
$workspace = $vc.GetWorkspace($workspacePath)
# get full download every time (tf get /force /recursive)
function DownloadAllFiles([Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspace, [string] $projectPath) {
$recursionType = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
$versionSpec = [Microsoft.TeamFoundation.VersionControl.Client.LatestVersionSpec]::Instance
$itemSpec = new-object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($projectPath,$recursionType)
$getRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($projectPath,$recursionType,$versionSpec)
$getOptions = [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::GetAll -bor [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite
$workpaceStatus = $workspace.Get($getRequest, $getOptions)
write-output $workpaceStatus
}
# get delta download - changes only (retrieves all mapped items)
function DownloadWorkspaceUpdates([Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspace) {
$workpaceStatus = $workspace.Get()
write-output $workpaceStatus
}
# get delta download - changes only (retrieves specific mapped items)
function DownloadWorkspaceUpdates([Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspace, [string] $projectPath) {
$recursionType = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
$versionSpec = [Microsoft.TeamFoundation.VersionControl.Client.LatestVersionSpec]::Instance
$itemSpec = new-object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($projectPath,$recursionType)
$getRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($projectPath,$recursionType,$versionSpec)
$getOptions = [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite
$workpaceStatus = $workspace.Get($getRequest, $getOptions)
write-output $workpaceStatus
}
# force latest download (slower)
DownloadAllFiles -workspace $workspace -projectPath $projectPath
# download deltas (fast), all mappings
DownloadWorkspaceUpdates -workspace $workspace
# download deltas (fast), specific mapping
DownloadWorkspaceUpdates -workspace $workspace -projectPath $projectPath
This could easily be extended to support TfsClientCredentials (i.e. Alternate Access credentials). I prefer the powershell approach as it doesn't require compilation or an extra EXE to copy around.

How to automate Package Manager Console in Visual Studio 2013

My specific problem is how can I automate "add-migration" in a build process for the Entity Framework. In researching this, it seems the mostly likely approach is something along the lines of automating these steps
Open a solution in Visual Studio 2013
Execute "Add-Migration blahblah" in the Package Manager Console (most likely via an add-in vsextention)
Close the solution
This initial approach is based on my own research and this question, the powershell script ultimately behind Add-Migration requires quite a bit of set-up to run. Visual Studio performs that setup automatically when creating the Package Manager Console and making the DTE object available. I would prefer not to attempt to duplicate that setup outside of Visual Studio.
One possible path to a solution is this unanswered stack overflow question
In researching the NuGet API, it does not appear to have a "send this text and it will be run like it was typed in the console". I am not clear on the lines between Visual Studio vs NuGet so I am not sure this is something that would be there.
I am able to find the "Pacakage Manager Console" ironically enough via "$dte.Windows" command in the Package Manager Console but in a VS 2013 window, that collection gives me objects which are "Microsoft.VisualStudio.Platform.WindowManagement.DTE.WindowBase". If there is a way stuff text into it, I think I need to get it to be a NuGetConsole.Implementation.PowerConsoleToolWindow" through reviewing the source code I am not clear how the text would stuffed but I am not at all familiar with what I am seeing.
Worst case, I will fall back to trying to stuff keys to it along the lines of this question but would prefer not to since that will substantially complicate the automation surrounding the build process.
All of that being said,
Is it possible to stream commands via code to the Package Manager Console in Visual Studio which is fully initialized and able to support an Entity Framework "add-migration" command?
Thanks for any suggestions, advice, help, non-abuse in advance,
John
The approach that worked for me was to trace into the entity framework code starting in with the AddMigrationCommand.cs in the EntityFramework.Powershell project and find the hooks into the EntityFramework project and then make those hooks work so there is no Powershell dependency.
You can get something like...
public static void RunIt(EnvDTE.Project project, Type dbContext, Assembly migrationAssembly, string migrationDirectory,
string migrationsNamespace, string contextKey, string migrationName)
{
DbMigrationsConfiguration migrationsConfiguration = new DbMigrationsConfiguration();
migrationsConfiguration.AutomaticMigrationDataLossAllowed = false;
migrationsConfiguration.AutomaticMigrationsEnabled = false;
migrationsConfiguration.CodeGenerator = new CSharpMigrationCodeGenerator(); //same as default
migrationsConfiguration.ContextType = dbContext; //data
migrationsConfiguration.ContextKey = contextKey;
migrationsConfiguration.MigrationsAssembly = migrationAssembly;
migrationsConfiguration.MigrationsDirectory = migrationDirectory;
migrationsConfiguration.MigrationsNamespace = migrationsNamespace;
System.Data.Entity.Infrastructure.DbConnectionInfo dbi = new System.Data.Entity.Infrastructure.DbConnectionInfo("DataContext");
migrationsConfiguration.TargetDatabase = dbi;
MigrationScaffolder ms = new MigrationScaffolder(migrationsConfiguration);
ScaffoldedMigration sf = ms.Scaffold(migrationName, false);
}
You can use this question to get to the dte object and from there to find the project object to pass into the call.
This is an update to John's answer whom I have to thank for the "hard part", but here is a complete example which creates a migration and adds that migration to the supplied project (project must be built before) the same way as Add-Migration InitialBase -IgnoreChanges would:
public void ScaffoldedMigration(EnvDTE.Project project)
{
var migrationsNamespace = project.Properties.Cast<Property>()
.First(p => p.Name == "RootNamespace").Value.ToString() + ".Migrations";
var assemblyName = project.Properties.Cast<Property>()
.First(p => p.Name == "AssemblyName").Value.ToString();
var rootPath = Path.GetDirectoryName(project.FullName);
var assemblyPath = Path.Combine(rootPath, "bin", assemblyName + ".dll");
var migrationAssembly = Assembly.Load(File.ReadAllBytes(assemblyPath));
Type dbContext = null;
foreach(var type in migrationAssembly.GetTypes())
{
if(type.IsSubclassOf(typeof(DbContext)))
{
dbContext = type;
break;
}
}
var migrationsConfiguration = new DbMigrationsConfiguration()
{
AutomaticMigrationDataLossAllowed = false,
AutomaticMigrationsEnabled = false,
CodeGenerator = new CSharpMigrationCodeGenerator(),
ContextType = dbContext,
ContextKey = migrationsNamespace + ".Configuration",
MigrationsAssembly = migrationAssembly,
MigrationsDirectory = "Migrations",
MigrationsNamespace = migrationsNamespace
};
var dbi = new System.Data.Entity.Infrastructure
.DbConnectionInfo("ConnectionString", "System.Data.SqlClient");
migrationsConfiguration.TargetDatabase = dbi;
var scaffolder = new MigrationScaffolder(migrationsConfiguration);
ScaffoldedMigration migration = scaffolder.Scaffold("InitialBase", true);
var migrationFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".cs");
File.WriteAllText(migrationFile, migration.UserCode);
var migrationItem = project.ProjectItems.AddFromFile(migrationFile);
var designerFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".Designer.cs");
File.WriteAllText(designerFile, migration.DesignerCode);
var designerItem = project.ProjectItems.AddFromFile(migrationFile);
foreach(Property prop in designerItem.Properties)
{
if (prop.Name == "DependentUpon")
prop.Value = Path.GetFileName(migrationFile);
}
var resxFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".resx");
using (ResXResourceWriter resx = new ResXResourceWriter(resxFile))
{
foreach (var kvp in migration.Resources)
resx.AddResource(kvp.Key, kvp.Value);
}
var resxItem = project.ProjectItems.AddFromFile(resxFile);
foreach (Property prop in resxItem.Properties)
{
if (prop.Name == "DependentUpon")
prop.Value = Path.GetFileName(migrationFile);
}
}
I execute this in my project template's IWizard implementation where I run a migration with IgnoreChanges, because of shared entites with the base project. Change scaffolder.Scaffold("InitialBase", true) to scaffolder.Scaffold("InitialBase", false) if you want to include the changes.

Testing SharePoint List Workflow from Visual Studio 2010

I am trying to create a custom workflow in Visual Studio 2010 for SharePoint 2010 and have run into a problem. I have figured out how to deploy the workflow to the SharePoint site, but executing it results in an error. However, the error message is completely non-descriptive, so I want to find out if there is a way to execute it from Visual Studio so I can see where it fails, and possibly why.
I'm trying to simply create a new subsite based on a given ListItem.Title information.
How is it you go about debugging?
For reference, here is my code
class CreateSubsite : System.Workflow.ComponentModel.Activity
{
protected override System.Workflow.ComponentModel.ActivityExecutionStatus
Execute(System.Workflow.ComponentModel.ActivityExecutionContext executionContext)
{
createSite();
return System.Workflow.ComponentModel.ActivityExecutionStatus.Closed;
}
public void createSite()
{
using (SPSite currentSite = SPContext.Current.Site)
{
using (SPWeb currentWeb = SPContext.Current.Web)
{
SPList currentList = SPContext.Current.List;
SPListItem currentListItem = SPContext.Current.ListItem;
WorkflowContext workflow = new WorkflowContext();
SPSite parentSite = new SPSite(workflow.CurrentWebUrl);
SPWeb newSite = currentSite.AllWebs.Add(
currentListItem.Title.Replace(" ", "_"),
currentListItem.Title,
String.Empty, currentWeb.Language, "CI Template", false, false
);
}
}
}
}
Try to remove Using keyword from your code .You should not dispose your SPSite and SPWeb when you use SPContext because disposing of that object might actually break the workflow as it may still need a reference to that object for later use.
just rewrite your code without use using
public void createSite() {
SPSite currentSite = SPContext.Current.Site
SPWeb currentWeb = SPContext.Current.Web
//.... Rest of your code
Hope that help
Regards.

Installing a .net 2008 windows service

I just created a simple test Windows Service and am having trouble. I'm new to Windows Services so I don't know if I'm even doing this right.
namespace testWindowsService
{
public partial class Service1 : ServiceBase
{
public Service1()
{InitializeComponent();}
protected override void OnStart(string[] args)
{
FileStream fs = new FileStream(#"c:\temp\started.txt", FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter m_streamWriter = new StreamWriter(fs);
m_streamWriter.BaseStream.Seek(0, SeekOrigin.End);
m_streamWriter.WriteLine("Service Started on \n" + DateTime.Now.ToShortDateString() + " at " + DateTime.Now.ToShortTimeString());
m_streamWriter.Flush();
m_streamWriter.Close();
}
protected override void OnStop()
{
FileStream fs = new FileStream(#"c:\temp\stopped.txt", FileMode.OpenOrCreate, FileAccess.Write);
StreamWriter m_streamWriter = new StreamWriter(fs);
m_streamWriter.BaseStream.Seek(0, SeekOrigin.End);
m_streamWriter.WriteLine("Service Stopped \n" + DateTime.Now.ToShortDateString() + " at " + DateTime.Now.ToShortTimeString());
m_streamWriter.Flush();
m_streamWriter.Close();
}
}
}
Then I built the project and opened Command Prompt from Start -> All Programs -> Microsoft Visual Studio 2008 -> Visual Studio Tools -> Visual Studio 2008 Command Prompt. From the prompt I ran:
installutil C:\Users\myUser\Documents\MyServices\testWindowsService\testWindowsService\bin\Debug\testWindowsService.exe
But I get the error:
No public installers with the RunInstallerAttribute.Yes attribute could be found in the C:\Users\myUser\Documents\MyServices\testWindowsService\testWindowsService\bin\Debug\testWindowsService.exe assembly.
I've tried googling it but found a lot of dead ends and half answers.
Thank you
You need a create an installer. Have a read through these articles to see an example. In particular:
[RunInstallerAttribute(true)]
public class ProjectInstaller : Installer{
private ServiceInstaller serviceInstaller1;
private ServiceProcessInstaller processInstaller;
public MyProjectInstaller(){
// Instantiate installers for process and services.
processInstaller = new ServiceProcessInstaller();
serviceInstaller1 = new ServiceInstaller();
// The services run under the system account.
processInstaller.Account = ServiceAccount.LocalSystem;
// The services are started manually.
serviceInstaller1.StartType = ServiceStartMode.Manual;
serviceInstaller2.StartType = ServiceStartMode.Manual;
// ServiceName must equal those on ServiceBase derived classes.
serviceInstaller1.ServiceName = "Hello-World Service 1";
// Add installers to collection. Order is not important.
Installers.Add(serviceInstaller1);
Installers.Add(processInstaller);
}
}
You can quite easily add an installer class to your project in VS2008, it appears as an item type when adding a new item.

Resources