I'm creating a little stupid service as a joke, is supposed to create an "invincible" txt on the desktop which gets recreated if deleted.
It works when debugged but when I install the service it doesn't create the txt.
I have :
execute the service with admin rights adding this line of code:
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
to the app.manifest).
I checked the "the authorize to interact whit desktop" checkmark on the service proprieties.
the code is working when I debug (i use Topshelf) but when I install the service it does not work.
the code which creates the txt(and the constructor):
public Invincible()
{
_timer = new Timer(3000) { AutoReset = true };
_timer.Elapsed += TimerElapsed;
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
string[] frase = new string[] {"NON PUOI RIMUOVERE QUESTA MALEDIZIONE <3"};
string curFile = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)+"/Invincible_Curse.txt";
string curFile2 = "C:/temp/Demos/Invincible_Curse.txt";
if (!File.Exists(curFile))
{
File.AppendAllLines(curFile, frase);
}
}
the Main:
static void Main(string[] args)
{
var exitCode = HostFactory.Run(x =>
{
x.Service<Invincible>(s =>
{
s.ConstructUsing(Invincible => new Invincible());
s.WhenStarted(Invincible => Invincible.Start());
s.WhenStopped(Invincible => Invincible.Stop());
x.SetServiceName("InvincibleService");
x.SetDisplayName("Invincible Service");
x.SetDescription("Cerca di sopravvivere");
});
});
int exitCodeValue = (int)Convert.ChangeType(exitCode, exitCode.GetTypeCode());
Environment.ExitCode = exitCodeValue;
}
thanks for the help.
Your code seems okay, even though you could do with a bit of a refactoring.
For example move around this part like this:
var exitCode = HostFactory.Run(x =>
{
x.Service<Invincible>(s =>
{
s.ConstructUsing(Invincible => new Invincible());
s.WhenStarted(Invincible => Invincible.Start());
s.WhenStopped(Invincible => Invincible.Stop());
});
x.SetServiceName("InvincibleService");
x.SetDisplayName("Invincible Service");
x.SetDescription("Cerca di sopravvivere");
});
Your issue is that, the windows service that you are running is using a different Windows user account, it's not the Windows user account which you use for logging in and using your PC. So their desktop paths are different. Every user account on Windows has a different desktop path.
To fix it, simply make the service log on using the account which you want the txt file to appear to.
Related
My Application will not launch after Windows startup, the application is registered in the register editor with the right path to my application exe.
Register Editor ScreenShot
here is my code to register my application to the register editor
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
RegistryKey reg = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RUN", true);
if (e.PropertyName == nameof(isStartOnStartup))
{
if (isStartOnStartup)
{
reg.SetValue("CommunicationHub", Process.GetCurrentProcess().MainModule.FileName);
Settings.Default.Startup = true;
Settings.Default.Save();
}
else
{
reg.DeleteValue("CommunicationHub");
Settings.Default.Startup = false;
Settings.Default.Save();
}
}
}
I have checked EventViewer for crashes and have aswell added logging to the application root, both gave me nothing.
I have disabled UAC, to check if it was the administrator right that was hindering the application from running, still no result.
i feel lost, i hope someone can help me with the problem..
Found another way to do this, instead of having my application in the register editor, but in Task Schedular, it will launch the application on windows startup with the admin rights. BUT! Only if I tell task scheduler to launch it as "at log on" trigger and not on "at system startup". But if I combine td.Triggers.AddNew(TaskTriggerType.Boot); and td.Principal.UserId = "SYSTEM";
it will give me the status running but the UI is not showing. Why?.. How do I fix this?
Here is my updated code, and I found a library to do the schedular thing.
library: https://github.com/dahall/TaskScheduler
{
TaskService ts = new TaskService();
string program_path = Process.GetCurrentProcess().MainModule.FileName;
if (e.PropertyName == nameof(isStartOnStartup))
{
if (isStartOnStartup)
{
TaskDefinition td = ts.NewTask();
td.Principal.RunLevel = TaskRunLevel.Highest;
td.Triggers.AddNew(TaskTriggerType.Boot); // Not working
td.Principal.UserId = "SYSTEM"; // Run whether user is logged on or not. combined with line 132 runs the program but the ui is invicible
//td.Triggers.AddNew(TaskTriggerType.Logon); // Runs and shows the UI perfectly fine
td.Actions.Add(new ExecAction(program_path, null));
ts.RootFolder.RegisterTaskDefinition("CommunicationHub", td);
Settings.Default.Startup = true;
Settings.Default.Save();
}
else
{
ts.RootFolder.DeleteTask("CommunicationHub", false);
Settings.Default.Startup = false;
Settings.Default.Save();
}
}
}
I create some method(this is only test method, to isolate problem from very very big system):
private void CompileHalloWorld()
{
System.Threading.Thread.Sleep((30000));
if (!Directory.Exists(_workingDir))
{
Directory.CreateDirectory(_workingDir);
}
Directory.SetCurrentDirectory(_workingDir);
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = true;
CompilerResults results = null;
try
{
results = csc.CompileAssemblyFromSource(parameters,
#"using System;
class Program {
public static void Main(string[] args) {
Console.WriteLine(""Hallo World!"");
}
}");
}
catch (Exception e)
{
int a = 2;
}
results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
}
On 99% machines this method work good, but on one of machines is some issue. When server work some times (few days), something broke in Windows I think. Then, when I run CompileHalloWorld() from exe app all work fine, but when I run this method from simple empty service, after invoke CompileAssemblyFromSourceinvoke in results structure is no errors in collection, but csc.exe return exit code -1073741502... After reboot of server all work good again, but I can't restart server every day...
I try to find some solution in SO. I check in Task Manager and no csc.exe proces hang, no Visual Studio work, no VBCSCompiler.exe hangs...
Please help me.
How can I programmatically log in to windows to create a Windows Logon Session?
I need a way that works from a WinForms app, from a Console app, and (most important) from a Windows Service.
One other requirement is that I need it to work on a the local system that the program/service is running on and also for remote systems.
If there's a way to do this using pInvoke/Win32 API I am open to that too.
I found these similar questions/answers in my research:
Programmatically create and launch and RDP session (without gui)
The answer here says it's possible but and gives a link but the sample code from the link doesn't work
Create a Windows Session from a service via the Win32 API
No Solution to the question asked
Create Windows session programmatically
No Solution but the OP mentioned in a comment that http://freerdp.com worked for him.
I've created a simple utility that I believe meets all the requirements in the question. You'll need to add a COM reference to Microsoft Terminal Services Active Client 1.0 Type Library (ActiveX).
I thought it might not work for creating a session on the local machine but I tested in in 2012R2 running as a Service and it actually can. The same exact method can be called from a WinForms app or from a Console app. When launched from a WinForms or Console app, the a form is shown for a few seconds so I made sure to set the control to enabled = false so it can't be interacted with.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using AxMSTSCLib;
namespace Utility.RemoteDesktop
{
public class Client
{
private int LogonErrorCode { get; set; }
public void CreateRdpConnection(string server, string user, string domain, string password)
{
void ProcessTaskThread()
{
var form = new Form();
form.Load += (sender, args) =>
{
var rdpConnection = new AxMSTSCLib.AxMsRdpClient9NotSafeForScripting();
form.Controls.Add(rdpConnection);
rdpConnection.Server = server;
rdpConnection.Domain = domain;
rdpConnection.UserName = user;
rdpConnection.AdvancedSettings9.ClearTextPassword = password;
rdpConnection.AdvancedSettings9.EnableCredSspSupport = true;
if (true)
{
rdpConnection.OnDisconnected += RdpConnectionOnOnDisconnected;
rdpConnection.OnLoginComplete += RdpConnectionOnOnLoginComplete;
rdpConnection.OnLogonError += RdpConnectionOnOnLogonError;
}
rdpConnection.Connect();
rdpConnection.Enabled = false;
rdpConnection.Dock = DockStyle.Fill;
Application.Run(form);
};
form.Show();
}
var rdpClientThread = new Thread(ProcessTaskThread) { IsBackground = true };
rdpClientThread.SetApartmentState(ApartmentState.STA);
rdpClientThread.Start();
while (rdpClientThread.IsAlive)
{
Task.Delay(500).GetAwaiter().GetResult();
}
}
private void RdpConnectionOnOnLogonError(object sender, IMsTscAxEvents_OnLogonErrorEvent e)
{
LogonErrorCode = e.lError;
}
private void RdpConnectionOnOnLoginComplete(object sender, EventArgs e)
{
if (LogonErrorCode == -2)
{
Debug.WriteLine($" ## New Session Detected ##");
Task.Delay(10000).GetAwaiter().GetResult();
}
var rdpSession = (AxMsRdpClient9NotSafeForScripting)sender;
rdpSession.Disconnect();
}
private void RdpConnectionOnOnDisconnected(object sender, IMsTscAxEvents_OnDisconnectedEvent e)
{
Environment.Exit(0);
}
}
}
On a side note I found this question that says there may be a way to use the ActiveX control (for RDP) without using a windows form at all. I saw the example they gave and I was unsure hot to use their code for this situation.
ActiveX control without a form
If there's anyone out there who understands how to do this without hosting the ActiveX control on a Form please post an example.
I'm trying to create a windows service in C# that will copy all the files from a network drive and paste it into a local drive (let's say in C drive). When I run the test case, the program runs successfully but when I install and run the windows service, the 'Access is denied' error comes in the log file.
I tried Map Network Drive (API) solution but that solution didn't work. either.
Here's the sample code that I've used to get all the files from a network drive and paste it into the local drive folder
Service1.cs
public partial class Service1 : ServiceBase
{
private Timer _timer;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
try
{
DoWork();
}
catch (Exception e)
{
WriteErrorLog(e);
}
}
private void DoWork()
{
_timer = new Timer();
_timer.Interval = 5000;
_timer.Enabled = true;
_timer.Elapsed += _timer_Elapsed;
Update();
}
private void Update()
{
RevidAddinController.Update_AutodeskAddinFolder_With_ArchcorpUpdatedAddinFiles(Configuration.AutodeskVersion, Configuration.AutodeskRevitAddinFolderPath);
}
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Update();
}
private void WriteErrorLog(Exception ex)
{
StreamWriter sw = null;
try
{
sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\Logfile.txt", true);
sw.WriteLine(DateTime.Now.ToString() + " ; " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim());
sw.Flush();
sw.Close();
}
catch
{
}
}
protected override void OnStop()
{
}
}
RevidAddinController.cs
public static class RevidAddinController
{
public static IEnumerable<AddinStatus> Update_AutodeskAddinFolder_With_ArchcorpUpdatedAddinFiles(List<string> autoDeskVersion, string addinInstallationPath)
{
var networkDrive = ActivateNetworkDrive();
var allAutodeskVersionPath = Util.GetAllAutodeskAddinLibraryFolderPaths(autoDeskVersion, addinInstallationPath);
List<FileData> latestArchcorpAddins = new List<FileData>();
foreach (var autodeskAddinFolder in allAutodeskVersionPath)
{
var archorpAddinFiles = Util.GetAllExternalRevitAddinFilesFromArchcorpAddinFolderPath(Configuration.ArchcorpAddinFolderPath);
var autodeskAddinFiles = Util.GetAllExternalRevitAddinFilesLocationFromAutodeskAddinFolderPath(autodeskAddinFolder);
var latestAddins = Util.GetUpdatedRevitAddinFromArchcorpFolderPath(autodeskAddinFolder, archorpAddinFiles, autodeskAddinFiles)
.Where(addin => !addin.FileName.Contains(Configuration.DeleteAddinNamePrefix));
latestArchcorpAddins.AddRange(latestAddins);
}
List<AddinStatus> addinCopyStatus = new List<AddinStatus>();
foreach (var autodeskAddinPath in allAutodeskVersionPath)
{
foreach (var newArchcorpAddin in latestArchcorpAddins)
{
addinCopyStatus.Add(Util.InstallNewAddinFile(newArchcorpAddin, autodeskAddinPath));
}
}
return addinCopyStatus;
}
/// <summary>
/// Map the network drive path
/// </summary>
/// <returns></returns>
public static NetworkDrive ActivateNetworkDrive()
{
NetworkDrive oNetDrive = new aejw.Network.NetworkDrive();
try
{
oNetDrive.LocalDrive = "O:";
oNetDrive.ShareName = #"\\acdxbfs1\Organisation";
oNetDrive.Force = true;
oNetDrive.Persistent = true;
oNetDrive.MapDrive();
}
catch (Exception err)
{
throw err;
}
return oNetDrive;
}
}
The complete code can be found on the gist here. Would really appreciate if someone reviews the code and provides any feedback/solution to this problem.
Running a service under the default Local System Account, will have no concept of the share. These are set up under user accounts.
Your 2 options
Run your service under a User Account which has those shares mapped
Access your share via and ip address instead of the drive letter. However, you will need to set the file/folder permissions accordingly.
The service does run as Local System (as previously named). If you have a mapped network drive to a local drive letter, the service cannot use it (because a mapped network drive is always only mapped for the user context, not the whole computer/system). However the service can access the share by UNC \\server\share. You can view the UNC path if you only have a mapped network drive by typing 'net use' inside a command prompt.
If you run your program as a user Windows does automatically authenticate you at the remote share (if not already done by adding a mapped network drive). Therefor Local System is the computer account you need to set the access permissions of the target share to the computername eg workstation1$ (only available inside a domain cause a workgroup does not know the other computers). This has to be done for the file permissions and the share permissions because both are independent and can restrict you from the access.
As an alternative you can authenticate at the remote network share with an user and password - there is an excellent thread in stackoverflow which you can find here which does show how you can achieve this.
Naturally you can also set the service to a user/password in the services manager (services.msc - double click your service and go to the logon tab) who has access to the share. By doing this, the user will be granted the 'login as service' permission which is necessary for this.
If the network file is shared with the local system account then you need to Log In as "Local System Account",
The advantage of running your services as the "Local System account" is that the service has complete unrestricted access to local resources.
But there are some disadvantages also, so be careful to not install unauthorized services as service gets full unrestricted access. Also if the service has some bugs it may lead to performance issues.
I created a Windows service application. It runs depending on the app settings which is included in the app.config file. It will be installed on different locations (networks, PCs) at the same time. Each location will need to set its own parameters in the app.config file. So I don't want it to run automatically after installation. By doing that each location users will be able to open configuration file and change it. Then they can start the service. But after that the service will run forever. Even when they restart the windows, it will run automatically after Windows opened.
Here is my installer class. It doesn't run automatically after installation. That's good. But if I run it manually and restart PC, when restart complete, it still waits to be started manually. What should I do?
public partial class MyServiceInstaller : System.Configuration.Install.Installer
{
ServiceInstaller _serviceInstaller = new ServiceInstaller();
ServiceProcessInstaller _processInstaller = new ServiceProcessInstaller();
string _serviceName = "MyService";
string _displayName = "My Service";
string _description = "My Service - Windows Service";
public MyServiceInstaller()
{
InitializeComponent();
this.BeforeInstall += new InstallEventHandler(MyServiceInstaller_BeforeInstall);
_processInstaller.Account = ServiceAccount.LocalSystem;
_serviceInstaller.StartType = ServiceStartMode.Automatic;
_serviceInstaller.Description = _description;
_serviceInstaller.ServiceName = _serviceName;
_serviceInstaller.DisplayName = _displayName;
Installers.Add(_serviceInstaller);
Installers.Add(_processInstaller);
}
protected override void OnCommitted(System.Collections.IDictionary savedState)
{
ServiceController sc = new ServiceController(_serviceName);
if (sc.Status != ServiceControllerStatus.Running)
{
TimeSpan timeout = TimeSpan.FromMilliseconds(10000);
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running, timeout);
sc.Stop();
}
else
{
RestartService(10000);
}
}
private void RestartService(int timeoutMiliseconds)
{
ServiceController service = new ServiceController(_serviceName);
int millisec1 = Environment.TickCount;
TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMiliseconds);
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
int millisec2 = Environment.TickCount;
timeout = TimeSpan.FromMilliseconds(timeoutMiliseconds - (millisec2 - millisec1));
service.Start();
service.WaitForStatus(ServiceControllerStatus.Running, timeout);
}
void MyServiceInstaller_BeforeInstall(object sender, System.Configuration.Install.InstallEventArgs e)
{
List<ServiceController> services = new List<ServiceController>(ServiceController.GetServices());
foreach (ServiceController s in services)
{
if (s.ServiceName == this._serviceInstaller.ServiceName)
{
ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
ServiceInstallerObj.Context = new System.Configuration.Install.InstallContext();
ServiceInstallerObj.Context = Context;
ServiceInstallerObj.ServiceName = _serviceName;
ServiceInstallerObj.Uninstall(null);
}
}
}
}
You can make this easier for yourself, by downloading the NuGet Package called TopShelf.
To summarize briefly, quoting from their page:
Topshelf is a framework for hosting services written using the .NET
framework. The creation of services is simplified, allowing developers
to create a simple console application that can be installed as a
service using Topshelf. The reason for this is simple: It is far
easier to debug a console application than a service. And once the
application is tested and ready for production, Topshelf makes it easy
to install the application as a service.
Your code already seems to be setting the startup type to Automatic.
Can you check the event log to see if your service is attempting to start automatically, but failing. This can happen if your service depends on another service which has not yet started.
You can use sc config after service start to modify start type.
eg.
sc config yourservicename start=auto
Or you can use ChangeServiceConfig.