How to launch a Winforms app from a windows-service - winforms

Hello i am trying to launch an interactive process - a winforms app - from a windows service on the current session.
I do not know how to do that.I am using TopShelf and i tried launching the process using the WhenSessionChanged hook provided by TopShelf.
I can get the sessionId
var exitCode = HostFactory.Run(x => {
x.Service<MyService>(s => {
s.ConstructUsing((h) => new MyService());
s.WhenStarted(t => t.Run());
s.WhenStopped(t => t.Stop());
s.WhenSessionChanged((anubis, args) => {
string winformsPath="D://WinFormsApp/Wf.exe"
int sessionId = args.SessionId;
Process process = new Process();
process.StartInfo = new ProcessStartInfo(winformsPath);
process.Start();
});
});
x.RunAsLocalSystem();
x.SetServiceName(Constants.ISO.NAME);
x.SetDisplayName(Constants.ISO.DISPLAY);
x.SetDescription(Constants.ISO.DESCRIPTION);
});
I do not know what to do with the sessionId.
P.S
I did not post the definition of MyService because it is not important in our case.I just want to launch the winforms on the current session.

Related

Why do accessing Client certs for AAD Auth require console Apps to Run as Administrator? Does it have too?

I have a basic console app that I want to access Azure Key Vault for a connection string. Been told over and over to use client certs in production environment for AAD Authentication. Problem is doing so seems to force the console app to be "Run as Administrator" triggering the UAC, making it impossible to be scheduled unattended.
Relevant code:
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using System.Security.Cryptography.X509Certificates;
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
...
.
})
.UseSerilog()
.ConfigureAppConfiguration(config =>
{
...
var credential = new ClientCertificateCredential(tenantId, clientID, GetCertificate(thumbPrint));
var client = new SecretClient(kvUri, credential);
config.AddAzureKeyVault(client, new AzureKeyVaultConfigurationOptions());
})
.Build();
static X509Certificate2 GetCertificate(string thumbprint)
{
var store = new X509Store(StoreName.Root);
try
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var collection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (collection.Count == 0) throw new System.Exception("Certificate is not installed.");
return collection[0];
}
finally
{
store.Close();
}
}
At this point I'm down to making it a Windows Background Service...

Correct Flow for Google OAuth2 with PKCE through Client App to SAAS API Server

So we are working on a client application in Windows WPF. We want to include Google as a login option and intend to go straight to the current most secure method. At the moment we have spawned a web browser with the following methods to obtain a Authorization Code
private async void HandleGoogleLogin() {
State.Token = null;
var scopes = new string[] { "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "openid" };
var request = GoogleOAuthRequest.BuildLoopbackRequest(scopes);
var listener = new HttpListener();
listener.Prefixes.Add(request.RedirectUri);
listener.Start();
// note: add a reference to System.Windows.Presentation and a 'using System.Windows.Threading' for this to compile
await Dispatcher.Invoke(async () => {
googleLoginBrowser.Address = request.AuthorizationRequestUri;
});
// here, we'll wait for redirection from our hosted webbrowser
var context = await listener.GetContextAsync();
// browser has navigated to our small http servern answer anything here
string html = string.Format("<html><body></body></html>");
var buffer = Encoding.UTF8.GetBytes(html);
context.Response.ContentLength64 = buffer.Length;
var stream = context.Response.OutputStream;
var responseTask = stream.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
stream.Close();
listener.Stop();
});
string error = context.Request.QueryString["error"];
if (error != null)
return;
string state = context.Request.QueryString["state"];
if (state != request.State)
return;
string code = context.Request.QueryString["code"];
await APIController.GoogleLogin(request, code, (success, resultObject) => {
if (!success) {
//Handle all request errors (username already exists, email already exists, etc)
} else {
((App)Application.Current).UserSettings.Email = resultObject["email"].ToString();
((App)Application.Current).SaveSettings();
}
attemptingLogin = false;
});
}
and
public static GoogleOAuthRequest BuildLoopbackRequest(params string[] scopes) {
var request = new GoogleOAuthRequest {
CodeVerifier = RandomDataBase64Url(32),
Scopes = scopes
};
string codeChallenge = Base64UrlEncodeNoPadding(Sha256(request.CodeVerifier));
const string codeChallengeMethod = "S256";
string scope = BuildScopes(scopes);
request.RedirectUri = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
request.State = RandomDataBase64Url(32);
request.AuthorizationRequestUri = string.Format("{0}?response_type=code&scope=openid%20profile{6}&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
AuthorizationEndpoint,
Uri.EscapeDataString(request.RedirectUri),
ClientId,
request.State,
codeChallenge,
codeChallengeMethod,
scope);
return request;
}
To my understanding, from this point the client app has completed the required portion to have the user login to their google account and approve any additional privileges.
Our API/App server is in GoLang.
APIController.GoogleLogin
from above sends the CodeVerifier and AuthorizationCode to the GoLang application server to then finish off the OAuth2 Flow.
Is this the correct flow given our client-server setup?
If so, what is the best practice for the Go Server to retrieve a Access Token/Refresh Token and get user information? Should the client app be performing a looping check-in to the app server as the app server will not immediately have the required information to login?
Thanks for the help!

PWA Push Notification not showing when app is not running

I'm developing a Progressive Web App with React that gets notifications when a new offer has been added to the DB. Everything works fine, I open the web, asks the user to grant permissions to enable notifications, we allow them, install the PWA, run it, add a new offer in the DB, and the a notification with the new offer gets displayed (Chrome + Windows 10).
But the issue is I don't get any notifications if the PWA is not running.. I would have thought the service worker is running in the background even if the PWA is closed. What am I missing?
here is my notifyNewOffer function in my notifications.ts file
function notifyNewOffer(newOffer: Offer) {
if ('serviceWorker' in navigator) {
const options = {
body: newOffer.subheading,
icon: './logo192.png',
image: './static/media/placeholder-offer.1bcbf040.png',
vibrate: [100, 50, 200],
badge: './favicon.ico',
tag: 'new-offers',
renotify: true,
actions: [
{ action: 'confirm', title: 'Check offer', icon: '' },
],
};
navigator.serviceWorker.ready.then(swreg => {
swreg.showNotification(newOffer.heading, options);
});
} else {
console.log('no serviceWorker');
}
}
And this is how I call it:
function addedOfferSubs<T>(setOffers: (offers:any) => void) {
// #ts-ignore
const subscription = API.graphql(graphqlOperation(addedOffer)).subscribe({
next: async (eventData: SubscriptionValue<T>) => {
const newOffer = (eventData.value.data as any).addedOffer;
await indexedDb.createObjectStore('offers', 'id'); // Opens db. Will create the table offers only if it doesnt already exist
await indexedDb.putValue('offers', newOffer); // Adds new offer
// Push notification
notifyNewOffer(newOffer);
// Set offers
const offersData = await getOffersFromIdb();
setOffers(offersData);
},
});
return () => subscription.unsubscribe()
}
Any ideas ?
Thanks very much
In order for notifications to appear when the app isn't open, you'll need to use Web Push as well. Web push allows you to send a notification from your server to device. When the push arrives on the device, it wakes up the service worker, and the notification is shown there.
Instructions for setting up Web Push & Notifications are available at https://developers.google.com/web/fundamentals/push-notifications

Running Nancy Self Host under TopShelf

I wrote a Nancy Self-Hosted service using topShelf at work (windows 7) and it worked just fine. I brought it home and run it under Windows 10, and I get the following error:
The Nancy self host was unable to start, as no namespace reservation existed for the provided url(s).
Please either enable UrlReservations.CreateAutomatically on the HostConfiguration provided to
the NancyHost, or create the reservations manually with the (elevated) command(s):
netsh http add urlacl url="http://+:5000/" user="Everyone"
I saw this suggestion:
HostConfiguration hostConfigs = new HostConfiguration()
{
UrlReservations = new UrlReservations() { CreateAutomatically = true }
};
But it only seems to work when running your own host, not with TopShelf. Here's my Main code:
public static void Main()
{
HostFactory.Run(x =>
{
//x.UseLinuxIfAvailable();
x.Service<SelfHost>(s =>
{
s.ConstructUsing(name => new SelfHost());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("SciData Recipe Data Interaction");
x.SetDisplayName("SciData.Recipe Service");
x.SetServiceName("SciData.Recipe");
});
}
Can someone suggest how to fix this so it runs under Windows 10? Thanks...
UPDATE:
The following did work: running a command shell as admin and typing the following seems to make everything work.
netsh http add urlacl url=http://+:1234/ user=DOMAIN\username
Where 1234 is the port the service uses. I'd still like to find out how to do this in the code, but if that doesn't work, this will suffice.
Take a look to Topshelf.Nancy also available as a NuGet package. It's doing the URL Reservation (netsh http) for you when you install the service. It will also be deleted when the service is uninstalled.
Add Topshelf.Nancy to your project
Add "WithNancyEndpoint" to your service
Your code:
public static void Main()
{
HostFactory.Run(x =>
{
//x.UseLinuxIfAvailable();
x.Service<SelfHost>(s =>
{
s.ConstructUsing(name => new SelfHost());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
s.WithNancyEndpoint(x, c =>
{
c.AddHost(port: 1234);
c.CreateUrlReservationsOnInstall();
});
});
x.RunAsLocalSystem();
x.SetDescription("SciData Recipe Data Interaction");
x.SetDisplayName("SciData.Recipe Service");
x.SetServiceName("SciData.Recipe");
});
}

application shutdown issue

in a prism application i have created a login form that start in a module. After the user is authenticated the login form wait for all application modules had been initialized (like a splah screen shows some information about the modules loaded), then starts the shell. All seems to work fine except when the user click on the cancel button of the login form. I would like that the application shutdown, but because the login form start in another thread i'm not able to use the Application.Current.Shutdown() (only work Enviroment.Exit(0) but i'm not sure this is a correct way). I try to invoke from Application.Current.Dispatcher but the application still run.
This is the login module initialization and where i catch the event that should do the application shutdown:
public void Initialize()
{
Dispatcher.CurrentDispatcher.BeginInvoke(
(Action) (() =>
{
Shell.Show();
EventAggregator.GetEvent<LoginStatusEvent>().Publish(new LoginStatusEvent { LoginStatus = LoginViewStatus.Close });
}));
ThreadStart showSplash =
() =>
{
Dispatcher.CurrentDispatcher.BeginInvoke(
(Action) (() =>
{
Container.RegisterType<LoginViewModel, LoginViewModel>();
Container.RegisterType<LoginView, LoginView>();
var login = Container.Resolve<LoginView>();
EventAggregator.GetEvent<LoginStatusEvent>().Subscribe(e => login.Dispatcher.BeginInvoke(
(Action)(() =>
{
if (e.LoginStatus == LoginViewStatus.Aborted)
{
login.Close();
Application.Current.Dispatcher.BeginInvoke((Action)(() => Application.Current.Shutdown()));
//Environment.Exit(0);
}
else if (e.LoginStatus == LoginViewStatus.Close)
{
login.Close();
}
else
{
WaitForCreation.Set();
}
})));
login.Show();
}));
Dispatcher.Run();
};
WaitForCreation = new AutoResetEvent(false);
var thread = new Thread(showSplash) {Name = "LoginView Thread", IsBackground = true};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
WaitForCreation.WaitOne();
}
any help is appreciated!
Why is your login UI running on the background thread? It seems the login UI should run on the GUI thread. Then you have a BackgroundWorker object (much easier to use than your code above and much more maintainable) that can run ProgressChanged anytime it needs to update the UI as the ProgressChanged method calls back to the GUI thread?
I often overload BackgroundWorker to be something like MyDataObjectBackgroundWorker, and I create a property that holds my object. This way I can pass data easily back and forth with ProgressChanged (like the information on your modules being loaded).

Resources