Adding jobs to a specific user's PrintQueue - c#

I have an application that does some central file generation based on user requests. What I want to be able to do with it once the files are created is to place them in that user's print queue ( in this organisation there is a central print queue so users are responsible for printing their own documents ) so that they can then be printed off when the user is ready.
By using the System.Printing assemblies in .net I am able to add a job to my own print queue, so I am sound on that part. My print code looks like this:
private void RunPrintJob( string myFileName )
{
PrintServer ps = new PrintServer(#"\\printatron");
PrintQueue queue = new PrintQueue(ps, "psandqueues");
try
{
PrintSystemJobInfo pj = queue.AddJob(myFileName);
Stream myStream = pj.JobStream;
Byte[] myByteBuffer = GenerateBufferFromFile(myFileName); myStream.Write(myByteBuffer, 0, myByteBuffer.Length);
myStream.Close();
}
catch (Exception ed)
{
Debug.WriteLine(ed.Message);
if (ed.InnerException != null)
{
Debug.WriteLine(" -> " + ed.InnerException);
}
result = false;
}
queue.Commit();
}
So I have my centrally created documents, I know which user was responsible for their creation and I can send them to the printer.
What I need now is a way to send them to the printer with the user who created them set as their user. Is there a way to do this through the print queue? I know it is readable from the PrintSystemJobInfo.Submitter property, but that is read-only. If not, do I have to do it through impersonation and if so in the latter case is there anything I can do to avoid having to store a bunch of user passwords and have the software fail every time the user changes their password? That seems like it would be a really clumsy way of operating, but as this activity isn't currently performed interactively what other options do I have?

I'm doing something similar. Impersonation is not too bad, if the process has sufficient permissions to get the level of impersonation you need (e.g. impersonation or delegation vs identification).
Here is what I do to impersonate:
public static bool GetImpersonationToken(string UPN, out IntPtr dupToken)
{
dupToken = IntPtr.Zero;
WindowsImpersonationContext impersonationContext = null;
bool result = false;
try
{
WindowsIdentity wid = new WindowsIdentity(UPN);
impersonationContext = wid.Impersonate();
result = DuplicateToken(wid.Token, 2, ref dupToken) != 0;
}
finally
{
if (impersonationContext != null)
impersonationContext.Undo();
}
return result;
}
Note: it is the calling method's responsibility to clean up that token handle.
I resolve the UPN from an LDAP query based on a users email (usually they are the same, but often enough they're not).
But I'm having some issues with the byte array that I'm passing in. I tried File.ReadAllBytes, but that causes the printer to spit out gibberish. Is there some special encoding that needs to happen in GenerateBufferFromFile?
** Update **
Looks like there are a bunch of issues around working with the JobStream directly:
Is PrintSystemJobInfo.JobStream broken?
So i'm just going to write to file, although I was hoping to avoid that.

Related

How can I get IEnumMoniker.Next to return monikers when running as administrator?

This code has been running fine for years inside a utility program. We recently updated the program to enforce UAC but we find this code only works when NOT running as administrator; the code inside the while loop is never executed when run as admin but the same code returns a list of moniker names when running unelevated.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace ROTExplorer
{
class Program
{
[DllImport("ole32.dll")]
static extern int GetRunningObjectTable(uint reserved, out IRunningObjectTable rot);
[DllImport("Ole32.Dll")]
static extern int CreateBindCtx(int reserved, out IBindCtx bindCtx);
static void Main(string[] args)
{
FindEntryInROT();
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
private static string FindEntryInROT()
{
IRunningObjectTable rot = null;
IBindCtx bindCtx = null;
IEnumMoniker enumMoniker = null;
IMoniker[] monikers = new IMoniker[1];
string displayName = null;
try
{
GetRunningObjectTable(0, out rot);
CreateBindCtx(0, out bindCtx);
rot.EnumRunning(out enumMoniker);
IntPtr fetched = IntPtr.Zero;
while (enumMoniker.Next(1, monikers, fetched) == 0)
{
string tempName;
monikers[0].GetDisplayName(bindCtx, null, out tempName);
Marshal.ReleaseComObject(monikers[0]);
monikers[0] = null;
try
{
Console.WriteLine(tempName);
}
catch
{
Console.WriteLine("Bad string");
}
}
}
catch (Exception ex)
{
Console.WriteLine("Failure while examining ROT: " + ex.Message);
}
finally
{
ReleaseCOMObject(monikers[0]);
ReleaseCOMObject(enumMoniker);
ReleaseCOMObject(bindCtx);
ReleaseCOMObject(rot);
}
Console.WriteLine(displayName);
return displayName;
}
private static void ReleaseCOMObject(object comObject)
{
if (comObject != null)
{
Marshal.ReleaseComObject(comObject);
comObject = null;
}
}
}
I've tried this on 2 machines. Can someone else please try this and confirm that this code only returns the moniker list when NOT running as administrator.
Does anyone have any thoughts about why the IEnumMoniker returns no monikers when running in an elevated process but returns a list when not run as admin?
I opened a ticket with Microsoft. It was escalated and I finally got an answer: it's working as designed. Here's the relevant conversation:
Microsoft Support:
The SCM/RPCSS service is where the Running Object Table lives. When the table is enumerated, the service does several checks. One of these checks is specifically to match the elevation level of the client token with the elevation level of the token's entry. If it doesn't match, then the entry will not be returned.
The behavior you are seeing is by design.
Me:
Can you send me a link to where this is documented? Having 'elevated' privileges should give the user access to more objects, not less. What you're describing seems to be akin to simply logging in as a different user.
Microsoft Support:
It’s not documented directly.
In some ways, your last statement is correct. An admin user has two security tokens, one for normal operation, and one for elevated. They are never used at the same time. https://learn.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works
When an administrator logs on, two separate access tokens are created for the user: a standard user access token and an administrator access token. The standard user access token contains the same user-specific information as the administrator access token, but the administrative Windows privileges and SIDs are removed.
I believe the reasoning behind all of this is security related, but I can’t explain that very well.
Me:
The administrator token has access to the standard user's files; why are the user's COM objects treated differently from the user's file objects?
Microsoft Support:
Because that’s the design decision the product group made. I don’t have further information on exactly why they made it like this.
Me:
It really sounds like a bug to me, or a faulty design. Is there a way to put this on the product group's radar?
Microsoft Support:
Unfortunately, there’s no leverage for getting any change in this area.

Sending email for several users once a week when someone log in

I am writing a program in C# using Windows Forms and I am stuck at this part.
When any user logs in to the program for the first time in any given week, an email should be sent to all users who have a task (the task will be assigned by an admin). If a user has no tasks, he/she should not receive an email. When the second user logs in for that week, emails should not be sent.
I mean when the any first user of the program logging in, the emails will be send for all users who has tasks (to remind them to do the task). The problem is I do not want one user to receive too many duplicate emails.I already have the code for sending the emails, but I need a way to handle the rest of the process. I have researched and I saw that Windows Services might be an option.
Is there another way to do this?
public int OutLook_Send_Email_To_User(string user_Email, string email_Subject, string email_Content)
{
try
{
Outlook.Application outApp = new Outlook.Application();
Outlook.MailItem outMsg = (Outlook.MailItem)outApp.CreateItem(Outlook.OlItemType.olMailItem);
Outlook.Recipient outTo = null;
outApp = new Outlook.Application();
outMsg = (Outlook.MailItem)outApp.CreateItem(Outlook.OlItemType.olMailItem);
outTo = (Outlook.Recipient)outMsg.Recipients.Add(user_Email);
outTo.Type = (int)Outlook.OlMailRecipientType.olTo;
outTo.Resolve();
outMsg.Subject = email_Subject;
outMsg.HTMLBody = email_Content;
outMsg.Save();
outMsg.Send();
outTo = null;
outMsg = null;
outApp = null;
return 0;
}
catch (Exception ex)
{
return -1;
}
}

Get All Processes With Their Corresponding App Domains

Is it possible to get a list of the running processes along with their corresponding app domains when running a program? I am aware mscoree.dll allows me to retrieve all App Domains of the current process using the ICorRuntimeHost.EnumDomains method. Is there a way to get this information without using an external API and just pure C# code? I understand mdbg has some functions that may help but I am not sure how to use this debugger. I am really looking for a solution using just C#.
Thanks
EDIT:
The goal is to show every process running along with their corresponding app domains on an html page. Ideally there would be a function that iterates through all running processes and retrieves this information.
Code that retrieves all app domains for current process:
private static List<AppDomainInf> GetAppDomains()
{
IList<AppDomain> mAppDomainsList = new List<AppDomain>();
List<AppDomainInf> mAppDomainInfos = new List<AppDomainInf>();
IntPtr menumHandle = IntPtr.Zero;
ICorRuntimeHost host = new CorRuntimeHost();
try
{
host.EnumDomains(out menumHandle);
object mTempDomain = null;
//add all the current app domains running
while (true)
{
host.NextDomain(menumHandle, out mTempDomain);
if (mTempDomain == null) break;
AppDomain tempDomain = mTempDomain as AppDomain;
mAppDomainsList.Add((tempDomain));
}
//retrieve every app domains detailed information
foreach (var appDomain in mAppDomainsList)
{
AppDomainInf domainInf = new AppDomainInf();
domainInf.Assemblies = GetAppDomainAssemblies(appDomain);
domainInf.AppDomainName = appDomain.FriendlyName;
mAppDomainInfos.Add(domainInf);
}
return mAppDomainInfos;
}
catch (Exception)
{
throw; //rethrow
}
finally
{
host.CloseEnum(menumHandle);
Marshal.ReleaseComObject(host);
}
}
using MdbgCore.dll located inside C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\MdbgCore.dll.:
CorPublish cp = new CorPublish();
foreach (CorPublishProcess process in cp.EnumProcesses())
{
foreach (CorPublishAppDomain appDomain in process.EnumAppDomains())
{
}
}

Programmatically get site status from IIS, gets back COM error

I am trying to programmatically get my site status from IIS to see if it's stopped, but I kept getting the following error,
The object identifier does not represent a valid object. (Exception from HRESULT: 0x800710D8)
The application is using ServerManager Site class to access the site status. Here is the code,
//This is fine, gets back the site
var serverManager = new Microsoft.Web.Administration.ServerManager(ConfigPath);
var site = serverManager.Sites.FirstOrDefault(x => x.Id == 5);
if (site == null) return;
var appPoolName = site.Applications["/"].ApplicationPoolName;
//error!
var state = site.State;
I've test with static site to isolate the issue, making sure that the site is up and running, all configuration are valid, point to the valid application pool...etc.
Let me know if you need more details. Is it the COM thing?
I figured out where the problem is. Basically, there are two parts to the Server manager, the first part of the server manager allows you to read site details from configuration file, which is what I've been doing above. The problem with that is you will only able get the information that's in file and site state is not part of it.
The second part of the Server Manager allows you to connect to the IIS directly and it does this by interacting with the COM element. So what I should be doing is this:
ServerManager manager= ServerManager.OpenRemote("testserver");
var site = manager.Sites.First();
var status = site.State.ToString() ;
I had a similar problem but mine was caused by the delay needed to activate the changes from the call to CommitChanges on the ServerManager object. I found the answer I needed here:
ServerManager CommitChanges makes changes with a slight delay
It seems like polling is required to get consistent results. Something similar to this solved my problem (I got the exception when accessing a newly added application pool):
...
create new application pool
...
sman.CommitChanges();
int i = 0;
const int max = 10;
do
{
i++;
try
{
if (ObjectState.Stopped == pool.State)
{
write_log("Pool was stopped, starting: " + pool.Name);
pool.Start();
}
sman.CommitChanges();
break;
}
catch (System.Runtime.InteropServices.COMException e)
{
if (i < max)
{
write_log("Waiting for IIS to activate new config...");
Thread.Sleep(1000);
}
else
{
throw new Exception(
"CommitChanges timed out efter " + max + " attempts.",
e);
}
}
} while (true);
...

How to programatically check if NServiceBus has finished processing all messages

As part of an effort to automate starting/stopping some of our NServiceBus services, I'd like to know when a service has finished processing all the messages in it's input queue.
The problem is that, while the NServiceBus service is running, my C# code is reporting one less message than is actually there. So it thinks that the queue is empty when there is still one message left. If the service is stopped, it reports the "correct" number of messages. This is confusing because, when I inspect the queues myself using the Private Queues view in the Computer Management application, it displays the "correct" number.
I'm using a variant of the following C# code to find the message count:
var queue = new MessageQueue(path);
return queue.GetAllMessages().Length;
I know this will perform horribly when there are many messages. The queues I'm inspecting should only ever have a handful of messages at a time.
I have looked at
other
related
questions,
but haven't found the help I need.
Any insight or suggestions would be appreciated!
Update: I should have mentioned that this service is behind a Distributor, which is shut down before trying to shut down this service. So I have confidence that new messages will not be added to the service's input queue.
The thing is that it's not actually "one less message", but rather dependent on the number of messages currently being processed by the endpoint which, in a multi-threaded process, can be as high as the number of threads.
There's also the issue of client processes that continue to send messages to that same queue.
Probably the only "sure" way of handling this is by counting the messages multiple times with a delay in between and if the number stay zero over a certain number of attempts that you can assume the queue is empty.
WMI was the answer! Here's a first pass at the code. It could doubtless be improved.
public int GetMessageCount(string queuePath)
{
const string query = "select * from Win32_PerfRawData_MSMQ_MSMQQueue";
var query = new WqlObjectQuery(query);
var searcher = new ManagementObjectSearcher(query);
var queues = searcher.Get();
foreach (ManagementObject queue in queues)
{
var name = queue["Name"].ToString();
if (AreTheSameQueue(queuePath, name))
{
// Depending on the machine (32/64-bit), this value is a different type.
// Casting directly to UInt64 or UInt32 only works on the relative CPU architecture.
// To work around this run-time unknown, convert to string and then parse to int.
var countAsString = queue["MessagesInQueue"].ToString();
var messageCount = int.Parse(countAsString);
return messageCount;
}
}
return 0;
}
private static bool AreTheSameQueue(string path1, string path2)
{
// Tests whether two queue paths are equivalent, accounting for differences
// in case and length (if one path was truncated, for example by WMI).
string sanitizedPath1 = Sanitize(path1);
string sanitizedPath2 = Sanitize(path2);
if (sanitizedPath1.Length > sanitizedPath2.Length)
{
return sanitizedPath1.StartsWith(sanitizedPath2);
}
if (sanitizedPath1.Length < sanitizedPath2.Length)
{
return sanitizedPath2.StartsWith(sanitizedPath1);
}
return sanitizedPath1 == sanitizedPath2;
}
private static string Sanitize(string queueName)
{
var machineName = Environment.MachineName.ToLowerInvariant();
return queueName.ToLowerInvariant().Replace(machineName, ".");
}

Categories