How can i lauch a process which has a UI from Windows service? - windows-7-x64

I have created a service application and also a windows form application now i want to lauch windows application from the service. I know that in win7 because of service isoloation
you con't do this directly so i used 'CreateProcessAsUser' of 'advapi32.dll' method but it is able to create the process also appearing in 'Task manager' but UI will not be displeyd to the user. What is the reason ? anybody can help me out of this?
Ok let me give the code which i have written
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, EntryPoint = "CreateProcessAsUser")]
public static extern bool CreateProcessAsUser(IntPtr hToken,string lpApplicationName,string lpCommandLine,ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,bool bInheritHandles,int dwCreationFlags,string lpEnvironment,string lpCurrentDirectory,ref STARTUPINFO lpStartupInfo,ref PROCESS_INFORMATION lpProcessInformation);
void LounchNewApplication()
{
try
{
string strAppName = #"D:\Working\UAC Demo\Tester\bin\Debug\Tester.exe";
string strAppPath = #"D:\Working\UAC Demo\Tester\bin\Debug\";
PROCESS_INFORMATION lpProcessInformation = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES lpProcessAttributes = new SECURITY_ATTRIBUTES();
lpProcessAttributes.nLength = (uint)Marshal.SizeOf(lpProcessAttributes);
STARTUPINFO lpStartupInfo = new STARTUPINFO();
lpStartupInfo.cb = Marshal.SizeOf(lpStartupInfo);
lpStartupInfo.lpDesktop = "WinSta0\\Default";
IntPtr htoken = IntPtr.Zero;
LogonUser("myName", "DomineName", "password", 2, 0, out htoken);
if (!CreateProcessAsUser(htoken, strAppName, null, ref lpProcessAttributes,
ref lpProcessAttributes, true, 0, null, strAppPath, ref lpStartupInfo,
ref lpProcessInformation))
{
eventLogger.WriteEntry("Error in starting application", EventLogEntryType.Error);
}
else
eventLogger.WriteEntry("Application launched successfulll" EventLogEntryType.Information);
//CloseHandle(lpProcessInformation.hThread);
//CloseHandle(lpProcessInformation.hProcess);
}
catch (Exception ex)
{
eventLogger.WriteEntry(ex.Message,
EventLogEntryType.Error);
}
}
I am calling LounchNewApplication() method OnStart of the service .

You are launching the process as the user but in session 0, which is non-interactive. Don't use LogonUser to create a user token. Use WTSQueryUserToken, passing in the session you want to create the process in. This token has the correct session ID. You can use WTSEnumerateSessions to list all sessions on the machine, or handle session change notifications in your service handler.

Technically, the service must be marked as interactive, eg. sc config <servicename> type= interact.
Services should no interact with the console, and definetely absolutely not in service startup. Luckly this was fixed post Windows 2003 and in Vista, Windows 2008 and Windows 7 is getting harder and harder to do such misbehavior.
The proper way is to separate your application into a service and a monitor application. The monitor runs as a normal application on the user session and communicates with the service via IPC.

Related

Powershell Invoke method neither throwing exception nor returning result

I am trying to build an ASP.Net, c# application to expose few IIS management related activities through web interface for a distributed Admin group.
I am making use of System.Management.Automation V3.0 library to wrap power shell commands. As a first step I wanted to list all Web Applications that are currently up and running on local IIS by invoking Get-WebApplication command.
This is where I am facing the issue. Method call is neither throwing any exception nor its returning the result. Does anyone know the root cause of this issue? Please share your experience of building such interface using System.Management.Automation.dll.
var shell = PowerShell.Create();
shell.Commands.AddScript("Get-WebApplication | Out-String");
try
{
var results = shell.Invoke();
if (results.Count > 0)
{
var builder = new StringBuilder();
foreach (var psObject in results)
{
builder.Append(psObject.BaseObject.ToString() + "\r\n");
}
}
}
catch(Exception ex)
{
throw;
}
PS: Get-Service in place of Get-WebApplication works absolutely fine by returning list of services available on the machine.
PowerShell.Create() does not create new PowerShell process. If you does not specify Runspace for it, then it will create new in-process Runspace. Since it run in your process, it will match your process bitness and you can not change that. To create Runspace with different bitness you need to create out of process Runspace. Here is a sample console application, which demonstrate how you can do that:
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public static class TestApplication {
public static void Main() {
Console.WriteLine(Environment.Is64BitProcess);
using(PowerShellProcessInstance pspi = new PowerShellProcessInstance()) {
string psfn = pspi.Process.StartInfo.FileName;
psfn=psfn.ToLowerInvariant().Replace("\\syswow64\\", "\\sysnative\\");
pspi.Process.StartInfo.FileName=psfn;
using(Runspace r = RunspaceFactory.CreateOutOfProcessRunspace(null, pspi)) {
r.Open();
using(PowerShell ps = PowerShell.Create()) {
ps.Runspace=r;
ps.AddScript("[Environment]::Is64BitProcess");
foreach(PSObject pso in ps.Invoke()) {
Console.WriteLine(pso);
}
}
}
}
}
}
If you compile this application as x32, it still will use x64 out of process Runspace on x64 operation system.

Minimum privilege for Powershell Remoting user to create a Task on Task Schedular

I want to enable running few jobs remotely on various Windows Nodes on some random interval. I can use PS remoting to logon to a remote computer and create a task there to run it under some specific user account.
In order for PS Remoting to work, I understand that I don't need to be an admin from the following article however I am unable to understand what is the minimum privilege needed to schedule a task on that remote machine for this script user.
I don't want to use an Admin account as this can be a security risk that I open up on those machines.
Can some one please help me out. I am using a C# application to initiate the remote powershell connection to that computer as below.
private void StartCollection(string triggerId, ActivationData parameters)
{
string scriptUserName = parameters.Params["PSUser"];
string scriptUserPassword = parameters.Params["PSUserPassword"];
string remotecomputerName = parameters.Params["ComputerName"];
string scheme = parameters.Params["PSScheme"];
string port = parameters.Params["PSPort"];
var agentFile = Path.Combine(parameters.AssemblyPath, parameters.FileName);
List<string> allParams = new List<string>();
allParams.Add(String.Format("-trigger {0} -collector {1} ", triggerId, parameters.CollectorId));
allParams.Add(String.Format("-monitored_by {0} -monitored_at {1}", parameters.Monitoring.Name, parameters.Monitoring.InterfacePoint));
foreach (var param in parameters.Params)
{
if (!ConfigurationKeys.Contains(param.Key))
{
allParams.Add(string.Format("-{0} {1}", param.Key, param.Value));
}
}
var cmdLineParams = string.Join(" ", allParams.ToArray());
// Cred to execute PS on the remote computer.
SecureString securePassword = scriptUserPassword.ToSecureString();
PSCredential scriptCred = new PSCredential(scriptUserName, securePassword);
var remoteUriStr = String.Format("{0}://{1}:{2}/wsman", scheme, remotecomputerName, port);
var remoteComputer = new Uri(remoteUriStr);
var connection = String.IsNullOrEmpty(scriptUserName) ? new WSManConnectionInfo(remoteComputer) : new WSManConnectionInfo(remoteComputer, null, scriptCred);
//connection.AuthenticationMechanism = AuthenticationMechanism.Credssp;
var runspace = RunspaceFactory.CreateRunspace(connection);
runspace.Open();
using (var powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
//Cred to run the agent under: Job credential.
SecureString jobPass = parameters.UserCred.Item2.ToSecureString();
string jonUser = parameters.UserCred.Item1;
PSCredential jobCred = new PSCredential(jonUser, jobPass);
var scriptfile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Start-Program.ps1");
Command command = new Command(scriptfile);
command.Parameters.Add("ComputerName", remotecomputerName);
command.Parameters.Add("StartTime", "22:50");
command.Parameters.Add("Program", agentFile);
command.Parameters.Add("Parameters", cmdLineParams);
command.Parameters.Add("TaskCredential", jobCred);
powershell.Commands.AddCommand(command);
var results = powershell.Invoke();
runspace.Close();
Console.WriteLine("Outputing the PS1 Result: {0}: " ,parameters.CollectorId);
foreach (var obj in results.Where(o => o != null))
{
Console.WriteLine("\t" + obj);
}
if (powershell.Streams.Error.Count > 0)
{
var errors = from err in powershell.Streams.Error select err.ErrorDetails.Message;
throw new Exception(String.Join(Environment.NewLine, errors.ToArray()));
}
}
}
Once you set the PS Remoting permissions, the simple way to give them Task Scheduling access would be to make your remote users a member of the local "Backup Operators" group on the target server. This would give them the necessary permissions to schedule and manage tasks on the server but would also give them indirect read access to files on that server.
If that's still too much access, then I've seen references to granting control of the C:\Windows\Tasks folder on the target server through ACLs but I've never tried that myself.

Deploying a project that includes an MDF file to an external computer

Basically, I have a Windows Forms Application that includes a dataGridView with the DataSource being an MDF file named VoRteXData.mdf. Now, I need to deploy this to an external location. For my forms code, it includes:
private void Form1_Load(object sender, EventArgs e)
{
SqlConnection sqlCon = new SqlConnection();
sqlCon.ConnectionString = #"Data Source=.\SQLEXPRESS;AttachDbFilename=C:\Users\Moderator\Documents\VoRteXData.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True";
sqlCon.Open();
SqlDataAdapter sda = new SqlDataAdapter("select * from VoRteXBanTable", sqlCon);
DataTable dt = new DataTable();
sda.Fill(dt);
dataGridView1.DataSource = dt;
}
private void button1_Click(object sender, EventArgs e)
{
string searchFilter = textBox1.Text;
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
if (dataGridView1.Rows[i].Cells[0].Value.ToString() == searchFilter)
{
dataGridView1.Rows[i].Selected = true;
dataGridView1.Rows[i].Visible = true;
}
else
{
dataGridView1.CurrentCell = null;
dataGridView1.Rows[i].Visible = false;
dataGridView1.Rows[i].Selected = false;
}
}
}
}
Next, in my MDF file is one table and 5 fields with around 50 records. Upon publishing the project to an external computer, I get an error: "A network-related or instance-specific error occurred while establishing a connection to SQL server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL server is configured to allow remote connections". However, I'm not using SQL Server Management Studio because I need to deploy this at an external location without a host. I don't want the user to install all the SQL prerequisites because, that would be ridiculous. So is there any way to get C# to run this MDF file at all? Or mabye upload it to the web for free?
The client machine must have SQL Server Express installed. You can include this in the setup.exe by including it as a prerequisite. This will only install the database engine. But, if you do not want the client machine to have SQL Server Express installed then you need to change your app to use SQL Server Compact. That way it will not need a SQL Server instance installed on the client.

How do I find the current version of a SQL Server data-tier application?

We are using a SQL Server Data-tier application (dacpac or DAC pack) and I'm having a hard time finding the current version of the database.
Is there a way to obtain the current version using any of these methods:
From within SQL Server Management Studio
Via a SQL statement
Programmatically using .NET code
From within SQL Server Management Studio
From http://msdn.microsoft.com/en-us/library/ee210574.aspx
To view the details of a DAC deployed to an instance of the Database Engine:
Select the View/Object Explorer menu.
Connect to the instance of the from the Object Explorer pane.
Select the View/Object Explorer Details menu.
Select the server node in Object Explorer that maps to the instance, and then navigate to the Management\Data-tier Applications node.
The list view in the top pane of the details page lists each DAC deployed to the instance of the Database Engine. Select a DAC to display the information in the detail pane at the bottom of the page.
The right-click menu of the Data-tier Applications node is also used to deploy a new DAC or delete an existing DAC.
Via a SQL statement
SELECT instance_name, type_version FROM msdb.dbo.sysdac_instances
Via a SQL statement on Azure
SELECT instance_name, type_version FROM master.dbo.sysdac_instances
Programmatically using .NET code
Note that in DacFx 3.0 this is no longer valid. See my other answer for a way to do it.
C#
ServerConnection serverConnection;
string databaseName;
// Establish a connection to the SQL Server instance.
using (SqlConnection sqlConnection =
new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
serverConnection = new ServerConnection(sqlConnection);
serverConnection.Connect();
// Assumes default database in connection string is the database we are trying to query.
databaseName = sqlConnection.Database;
}
// Get the DAC info.
DacStore dacstore = new DacStore(serverConnection);
var dacInstance = dacstore.DacInstances[databaseName];
System.Diagnostics.Debug.Print("Database {0} has Dac pack version {1}.", databaseName, dacInstance.Type.Version);
VB.NET
Dim serverConnection As ServerConnection
Dim databaseName As String
' Establish a connection to the SQL Server instance.
Using sqlConnection As New SqlConnection(ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString)
serverConnection = New ServerConnection(sqlConnection)
serverConnection.Connect()
' Assumes default database in connection string is the database we are trying to query.
databaseName = sqlConnection.Database
End Using
' Get the DAC info.
Dim dacstore As New DacStore(serverConnection)
Dim dacInstance = dacstore.DacInstances(databaseName)
System.Diagnostics.Debug.Print("Database {0} has Dac pack version {1}.", databaseName, dacInstance.Type.Version)
In DacFx 3.0 the DacStore is no longer available. To get the version from C# code you need to query the database. Here's an example:
using System;
using System.Data;
using System.Data.SqlClient;
class Program
{
static void Main()
{
try
{
string version = GetDatabaseVersion(#"Initial Catalog=xxx;Data Source=yyy;Integrated Security=True;Pooling=False", false);
Console.WriteLine("Database has DAC pack version {0}.", version);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.WriteLine();
Console.ResetColor();
}
Console.WriteLine("Press any key to exit");
Console.ReadKey(true);
}
/// <summary>
/// Gets the database version.
/// </summary>
/// <param name="connectionString">The connection string of database to query.</param>
/// <param name="isAzure">True if we are querying an Azure database.</param>
/// <returns>DAC pack version</returns>
private static string GetDatabaseVersion(string connectionString, bool isAzure)
{
var connectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
string instanceName = connectionStringBuilder.InitialCatalog;
string databaseToQuery = "msdb";
if (isAzure)
{
// On Azure we must be connected to the master database to query sysdac_instances
connectionStringBuilder.InitialCatalog = "Master";
databaseToQuery = "master";
}
string query = String.Format("select type_version from {0}.dbo.sysdac_instances WHERE instance_name = '{1}'", databaseToQuery, instanceName);
using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
command.CommandText = query;
command.CommandTimeout = 15;
command.CommandType = CommandType.Text;
var version = (string)command.ExecuteScalar();
return version;
}
}
}

When is a started service not a started service? (SQL Express)

We require programmatic access to a SQL Server Express service as part of our application. Depending on what the user is trying to do, we may have to attach a database, detach a database, back one up, etc. Sometimes the service might not be started before we attempt these operations. So we need to ensure the service is started. Here is where we are running into problems. Apparently the ServiceController.WaitForStatus(ServiceControllerStatus.Running) returns prematurely for SQL Server Express. What is really puzzling is that the master database seems to be immediately available, but not other databases. Here is a console application to demonstrate what I am talking about:
namespace ServiceTest
{
using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
class Program
{
private static readonly ServiceController controller = new ServiceController("MSSQL$SQLEXPRESS");
private static readonly Stopwatch stopWatch = new Stopwatch();
static void Main(string[] args)
{
stopWatch.Start();
EnsureStop();
Start();
OpenAndClose("master");
EnsureStop();
Start();
OpenAndClose("AdventureWorksLT");
Console.ReadLine();
}
private static void EnsureStop()
{
Console.WriteLine("EnsureStop enter, {0:N0}", stopWatch.ElapsedMilliseconds);
if (controller.Status != ServiceControllerStatus.Stopped)
{
controller.Stop();
controller.WaitForStatus(ServiceControllerStatus.Stopped);
Thread.Sleep(5000); // really, really make sure it stopped ... this has a problem too.
}
Console.WriteLine("EnsureStop exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
private static void Start()
{
Console.WriteLine("Start enter, {0:N0}", stopWatch.ElapsedMilliseconds);
controller.Start();
controller.WaitForStatus(ServiceControllerStatus.Running);
// Thread.Sleep(5000);
Console.WriteLine("Start exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
private static void OpenAndClose(string database)
{
Console.WriteLine("OpenAndClose enter, {0:N0}", stopWatch.ElapsedMilliseconds);
var connection = new SqlConnection(string.Format(#"Data Source=.\SQLEXPRESS;initial catalog={0};integrated security=SSPI", database));
connection.Open();
connection.Close();
Console.WriteLine("OpenAndClose exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
}
}
On my machine, this will consistently fail as written. Notice that the connection to "master" has no problems; only the connection to the other database. (You can reverse the order of the connections to verify this.) If you uncomment the Thread.Sleep in the Start() method, it will work fine.
Obviously I want to avoid an arbitrary Thread.Sleep(). Besides the rank code smell, what arbitary value would I put there? The only thing we can think of is to put some dummy connections to our target database in a while loop, catching the SqlException thrown and trying again until it works. But I'm thinking there must be a more elegant solution out there to know when the service is really ready to be used. Any ideas?
EDIT: Based on feedback provided below, I added a check on the status of the database. However, it is still failing. It looks like even the state is not reliable. Here is the function I am calling before OpenAndClose(string):
private static void WaitForOnline(string database)
{
Console.WriteLine("WaitForOnline start, {0:N0}", stopWatch.ElapsedMilliseconds);
using (var connection = new SqlConnection(string.Format(#"Data Source=.\SQLEXPRESS;initial catal
using (var command = connection.CreateCommand())
{
connection.Open();
try
{
command.CommandText = "SELECT [state] FROM sys.databases WHERE [name] = #DatabaseName";
command.Parameters.AddWithValue("#DatabaseName", database);
byte databaseState = (byte)command.ExecuteScalar();
Console.WriteLine("databaseState = {0}", databaseState);
while (databaseState != OnlineState)
{
Thread.Sleep(500);
databaseState = (byte)command.ExecuteScalar();
Console.WriteLine("databaseState = {0}", databaseState);
}
}
finally
{
connection.Close();
}
}
Console.WriteLine("WaitForOnline exit, {0:N0}", stopWatch.ElapsedMilliseconds);
}
I found another discussion dealing with a similar problem. Apparently the solution is to check the sys.database_files of the database in question. But that, of course, is a chicken-and-egg problem. Any other ideas?
Service start != database start.
Service is started when the SQL Server process is running and responded to the SCM that is 'alive'. After that the server will start putting user databases online. As part of this process, it runs the recovery process on each database, to ensure transactional consistency. Recovery of a database can last anywhere from microseconds to whole days, it depends on the ammount of log to be redone and the speed of the disk(s).
After the SCM returns that the service is running, you should connect to 'master' and check your database status in sys.databases. Only when the status is ONLINE can you proceed to open it.