I'm developing 'Share Monitoring Application' via C# and it's monitoring the sharing activities and I'm using these APIs to implement enumerate shared items/un-sharing shared items.
Api used:
NetShareEnum
NetShareDel
NetShareEnum to enumerate all shared items and NetShareDel to delete shared items (=unshare).
I used SHChangeNotify to remove shared mark and directories working fine. (Delete shared item using NetShareDel is not affected immediately.)
But printer state is not affected by SHChangeNotify. Which mean after deleting shared printer via NetShareDel and call SHChangeNotify with SHCNE_NETUNSHARE and SHCNF_PATHW. Also I used SHCNE_NETUNSHARE and SHCNF_PRINTERW too, but nothing happened.
Shared printer's state mark: http://i.stack.imgur.com/1ZGrI.png
In this picture, you can see the users the right side of check circle and that indicate printer is shared.
But after calling NetShareDel to unshared shared printer and it's succeed, but shared mark is disappear.
Anyone know how to implement this? I'm waiting for your help. :D
Sorry for my bad english.
Have you tried going via WMI?
I haven't used it myself to "unshare" a printer, but I use it alot in an application to edit printers and printer-ports in other ways.
I would think something like this should do the trick.
The Win32_Printer class looks like it has a "shared" property, so I would suggest trying to switch it to false.
https://msdn.microsoft.com/en-us/library/aa394363%28v=vs.85%29.aspx
I haven't tested this code with unsharing, but it is the exact same code I use to change other properties.
//get the printer(s) through wmi query
//prep query
SelectQuery query = new SelectQuery(string.Format("select * from Win32_Printer WHERE Name = '{0}'", "printername"));
//create scope (connect to server)
ManagementScope scope = new ManagementScope("\\\\serverName\\root\\cimv2");
//search for printers
ManagementObjectSearcher search = new ManagementObjectSearcher(scope, query);
//get collection of printers (should be 0 or 1, but it returns a collection regardless because of the query
ManagementObjectCollection printers = search.Get();
//iterate through the 0-1 printers and set Shared to false
foreach (ManagementObject printer in printers)
{
printer.SetPropertyValue("Shared",false);
printer.put();
}
I tried WMI and it works on my computer, but other computers throw an exception. And I think the reason of application throw an exception is the one of required library is missing on the computer.
So I'm looking for the API that can be used instead of the WMI.
Finally I found the GetPrinter and SetPrinter from the MSDN.
And also I found PRINTER_INFO_5 structure. According to MSDN, Attributes field indicate the printer's attribute including printer is shared or not. And this can be checked Attributes field has PRINTER_ATTRIBUTE_SHARED value.
Anyway, this problem can be solved only OpenPrinter, GetPrinter and SetPrinter.
This image shows the before and after calling 'UnsharePrinter' method.
This is the method I made to un-share the shared printer.
(Un-sharing the shared printer can be performed via NetShareDel, but it cannot notify printer is un-shared to the system.)
Boolean UnsharePrinter(String printerName) {
// fill PRINTER_DEFAULTS structure
// and set DesiredAccess to PRINTER_ACCESS_ADMINISTER to
// get rights to call SetPrinter
PRINTER_DEFAULTS pd;
pd.pDatatype = IntPtr.Zero;
pd.pDevMode = IntPtr.Zero;
pd.DesiredAccess = PRINTER_ACCESS_ADMINISTER;
IntPtr pDefaults = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PRINTER_DEFAULTS)));
Marshal.StructureToPtr(pd, pDefaults, true);
IntPtr hPrinter;
// open the printer
if ( !OpenPrinter(printerName, out hPrinter, pDefaults) ) {
Marshal.FreeHGlobal(pDefaults);
return false;
}
// first, call Zero pointer and 0 size to get minimum required space
IntPtr pInfo = IntPtr.Zero;
Int32 pcbNeeded;
GetPrinter(hPrinter, 5, pInfo, 0, out pcbNeeded);
// alloc reqiured space and call GetPrinter
pInfo = Marshal.AllocHGlobal(pcbNeeded);
if ( !GetPrinter(hPrinter, 5, pInfo, pcbNeeded, out pcbNeeded) ) {
Marshal.FreeHGlobal(pInfo);
ClosePrinter(hPrinter);
return false;
}
// pointer to structure
PRINTER_INFO_5 pi5 = (PRINTER_INFO_5) Marshal.PtrToStructure(pInfo, typeof(PRINTER_INFO_5));
Marshal.FreeHGlobal(pInfo);
// if printer is not shared, release the memory and exit
if ( (pi5.Attributes & PRINTER_ATTRIBUTE_SHARED) == 0 ) {
ClosePrinter(hPrinter);
return false;
}
// remove the shared flag
pi5.Attributes &= ~PRINTER_ATTRIBUTE_SHARED;
// alloc pointer and make structure as pointer
pInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PRINTER_INFO_5)));
Marshal.StructureToPtr(pi5, pInfo, true);
// set printer
Boolean r = SetPrinter(hPrinter, 5, pInfo, 0);
Marshal.FreeHGlobal(pInfo);
ClosePrinter(hPrinter);
return r;
}
Related
Objective: I am trying to create a simple console app whose functionality is to set default print preferences for my local machine. Any local application that accesses the printer will use this setting by default.
Below are the properties that am trying to set default values
Paper Type
Paper Orientation
Margin
Default Printer
I have implemented the below code with little use,
using (ManagementObjectSearcher objectSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_Printer"))
{
using (ManagementObjectCollection objectCollection = objectSearcher.Get())
{
foreach (ManagementObject mo in objectCollection)
{
if (string.Compare(mo["Name"].ToString(), "OneNote", true) == 0)
{
mo.InvokeMethod("SetDefaultPrinter", null, null);
return true;
}
}
}
}
Even the default printer get updated, but every time when I do ctrl+p a different printer is selected
Secondly, I have implemented the below code to modify HKCU, but it is not providing intended results.
string pageSetupKey = "Software\\Microsoft\\Internet Explorer\\PageSetup";
bool isWritable = true;
RegistryKey rKey = Registry.CurrentUser.OpenSubKey(pageSetupKey, isWritable);
rKey.SetValue("margin_bottom",1000, RegistryValueKind.DWord);
rKey.SetValue("margin_top", 1000, RegistryValueKind.DWord);
rKey.SetValue("margin_left", 1000, RegistryValueKind.DWord);
rKey.SetValue("margin_right", 1000, RegistryValueKind.DWord);
kindly clarify that what I intend to do is achievable and the ways to achieve it.
I have also referred to other posts and questions which are not helpful.
Note: I want to make these changes, not on the process/program basis but for the current user.
I need to get a list of all currently opened MSACCESS instances in the system (windows) to be able to close any of them from within my app. I have no problems with EXCEL and WINWORD but can't hook up with Access.
I use Office 2016 and I see that MSACCESS creates separate procss for each opened database file. So I think I have to get application instances from window handles. I've tried to adapt this code: How to iterate through instance of Excel c#
I'm able to get all MSACCESS processes but the Excel or Word code isn't working for MSACCESS. The Code line:
if (buf.ToString() == "EXCEL7")
Always gives me the MsoCommandBarDock value.
Any thoughts on how I can achieve this?
Based on the answer for Excel, the Access version is similar:
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
var procs = new List<Process>(Process.GetProcessesByName("MSACCESS.EXE"));
foreach (var p in procs)
{
var mainHandle = (int)p.MainWindowHandle;
if (mainHandle > 0)
{
var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
Microsoft.Office.Interop.Access.Application app = null;
int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
if (res >= 0)
{
Debug.Assert(app.hWndAccessApp == mainHandle);
Console.WriteLine(app.Name);
}
}
}
I tested it with Access 2016 on Windows 10, en-us locale.
The major difference is that the window hierarchy of access is not as convoluted as the one of Excel, therefore you can omit the iteration of child windows.
Disclaimer: This relies on the internal structure of a closed-source Windows application. Microsoft as its vendor discourages this kind of tricks for obvious reasons: they may ship and update or release a new version at any time where the inner structure (the window hierarchy) has changed, breaking code that relies on this. Also, MS Access used to have a single document view mode, which may present you with two versions of window hierarchy in the same release. Don't do this in commercial products / productive software.
According to the answer from Cee McSharpface, in 2021 (Microsoft Access for Microsoft 365 MSO (16.0.14326.20504) 64-bit and Windows 10 20H2) I had to adapt the solution as follows:
[DllImport("oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
int hwnd, uint dwObjectID, byte[] riid,
ref Microsoft.Office.Interop.Access.Application ptr);
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
var procs = new List<Process>(Process.GetProcessesByName("MSACCESS"));
foreach (var p in procs)
{
var mainHandle = (int)p.MainWindowHandle;
if (mainHandle > 0)
{
var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
Microsoft.Office.Interop.Access.Application app = null;
int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
if (res >= 0)
{
Debug.Assert(app.hWndAccessApp() == mainHandle);
Console.WriteLine(app.Name);
}
}
}
Please notice the following changes:
GetProcessesByName uses "MSACESS" instead of "MSACCESS.EXE", according to this documentation:
The process name is a friendly name for the process, such as Outlook,
that does not include the .exe extension or the path
AccessibleObjectFromWindow uses a ref Microsoft.Office.Interop.Access.Application ptr because there is no Window object like in the Excel interop.
There are many ways of doing this inlcuding retrieve COM Objects from ROT (running object table). Since your need is "just" to be able to close apps, following code should work fine.
using System.Diagnostics;
using System.Linq;
Process.GetProcessesByName("MSACCESS").All(x => x.CloseMainWindow());
This sends a close message to all Access main windows, which is similar to user closing the app.
I'm trying to get the total pages count from all the printers connected to a computer. To do that i try in different ways but nothing give me the expected result:
SNMP: Very useful but impossible use it on a local printer.
Win32_printer: No way to get the total page count of a network printer and more of the local printers not store the page count on it.
printui.dll: total page count not supported.
Now i'm trying a different approach: there is a way to programmatically print the device configuration page (Example: THAT) to a file? you can print it whitout connect the printer to a PC's so it shoud be stored somewhere in some digital format. the only thing i found on internet is the Bidi Communication but i can't found any documentation that can help me... I not need a particular programming language, but if possible i prefere c#,c++ or java.
#Soner Gönül: Ok, so i choose c# just because is the language that i use to write the snmp test, but examples in other languages are appreciated:
SNMP:
SNMP snmp = new SNMP();
// Aprire la connessione verso la stampante
try
{
snmp.Open(IPAddressOfPrinter, CommunityString, Retries, TimeoutInMS);
} catch {
MessageBox.Show("Unable to reach the printer");
}
//Sides Counter
lblSides.Text = snmp.Get(".1.3.6.1.2.1.43.10.2.1.4.1.1").ToString();
rtbLog.Text += "Printer Counter: " + lblSides.Text;
rtbLog.Text += " Sides.\n";
That code give me the number of sides printed by the printer, that is ok, but need that the printer is a network printer, on a USB printer i can't use SNMP.
Win32_Printer:
ConnectionOptions options = new ConnectionOptions();
ManagementScope scope = new ManagementScope(#"\\" + System.Environment.MachineName.ToString() + #"\root\cimv2"); //yeah... i know #"root\cimv2" is enought, i notice only now...
scope.Connect();
ObjectQuery PRNquery = new ObjectQuery("SELECT * FROM Win32_Printer");
ManagementObjectSearcher PRNsearcher = new ManagementObjectSearcher(scope, PRNquery);
ManagementObjectCollection PRNqueryCollection = PRNsearcher.Get();
foreach (ManagementObject m in PRNqueryCollection)
{
MessageBox.Show(m["JobCountSinceLastReset"].Tostring());
//But the result is Zero or random error the 90% of the times.
}
priuntui.dll:
If i launch
rundll32 printui.dll PrintUIEntry /something
or try to export the report with:
public static class PrintTest
{
[DllImport("printui.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void PrintUIEntryW(IntPtr hwnd,
IntPtr hinst, string lpszCmdLine, int nCmdShow);
public static void Print(string printerName)
{
var printParams = string.Format(#"/f C:\test.dat /Xg /n{0}", printerName);
PrintUIEntryW(IntPtr.Zero, IntPtr.Zero, printParams, 0);
}
}
i not found a page count report, but only generic information.
After reading this post on SO I tried to write a small app I need to read and write hidden registry keys/values.
I checked Registry Manipulation using NT Native APIs and Creating "Hidden" Registry Values links.
First one gave me something to work on, but it's written in C++ while second is a Delphi project working well.
I am not able to convert first and I could try to convert second, but I'd need to find some code to read keys/values too. For this reason I'd like to know if there's something "ready" and tested in C#.
I've also downloaded Proces Hacker v1.11 source code and used it to partially convert Delphi example as shown below, but hidden registry key is accessible (while in Delphi it wasn't) and there are not APIs to write values.
static void Main(string[] args)
{
string KeyNameBuffer = #"\Registry\User\S-1-5-21-3979903645-2167650815-2353538381-1001\SOFTWARE";
string NewKeyNameBuffer = "Systems Internals";
string HiddenKeyNameBuffer = "Can't touch me\0";
string HiddenValueNameBuffer = "Hidden Value";
// Apro la chiave di registro
IntPtr SoftwareKeyHandle = CreateKey(KeyNameBuffer, IntPtr.Zero);
if (SoftwareKeyHandle != IntPtr.Zero)
{
IntPtr SysKeyHandle = CreateKey(NewKeyNameBuffer, SoftwareKeyHandle);
if (SysKeyHandle != IntPtr.Zero)
{
// This key shouldn't be accessible, but it is
IntPtr HiddenKeyHandle = CreateKey(HiddenKeyNameBuffer, SysKeyHandle);
if (HiddenKeyHandle != IntPtr.Zero)
{
// I don't have APIs to write values
}
}
}
}
static IntPtr CreateKey(string keyName, IntPtr rootKey)
{
IntPtr res;
KeyCreationDisposition disp;
ObjectAttributes attributes = new ObjectAttributes(keyName,
ObjectFlags.CaseInsensitive,
new NativeHandle(rootKey));
NtStatus st = Win32.NtCreateKey(out res, KeyAccess.All,
ref attributes, 0,
IntPtr.Zero, RegOptions.NonVolatile, out disp);
return st == NtStatus.Success ? res : IntPtr.Zero;
}
Finally: from Vista on, you cannot write \Registry\Machine part if you're not running your app as Administrator, so in the example I used my user registry key. Is there a way to us native APIs to write that part of the registry if I need to store a per-machine value?
If you want it in HKLM and privileges don't let you, it doesn't matter which API layer you're using, Reg* functions of Nt* ones - it won't let you do that with access denied error.
My question is very simple, but i dint found an answer googling long time.
How to set REG_KEY_DONT_VIRTUALIZE flag to registry key created by me (i.e. HKLM\Software\MyApp)?
I want my program to be user-independent. Every user starting my app should have access to the same configuration options located in that location).
Changing application manifest I can disable registry virtualization by running program as administrator, but I want normal user be able to run the program and read registry values.
If you don't want your app to be virtualized then you use a manifest to indicate that. If you use REG_KEY_DONT_VIRTUALIZE on your key then all that will happen is that all the writes will fail because your users won't have write access to HKLM.
If you want all your users to share configuration then you'll have to store the configuration in a file rather than the registry. There's nowhere appropriate in the registry that is shared by all users and allows standard users write access.
This is pretty unclear, virtualization is only enabled for legacy non-UAC compatible programs and reading is always permitted. I have to assume that writing is the problem. Change the permissions on the key with, say, your installer or Regedit.exe so that Everybody has write access.
Without changing or adding ACLs to the key, you can ensure that the key you are using programmatically is viewing the 64-bit part of the registry by using the RegistryKey.OpenBaseKey API with the RegistryView.Registry64 flag.
This appears to work properly for 32-bit applications regardless of whether or not registry virtualization is enabled for the app.
private const string MyRegistryKeyPath = "Software\\My Company\\My App";
private static RegistryKey OpenMyAppRegistryKey(bool requireWriteAccess = false)
{
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
{
return requireWriteAccess
? baseKey.CreateSubKey(MyRegistryKeyPath, RegistryKeyPermissionCheck.ReadWriteSubTree)
: baseKey.OpenSubKey(MyRegistryKeyPath, RegistryKeyPermissionCheck.ReadSubTree);
}
}
If requireWriteAccess is false, this method will return null if the specified key does not exist.
I should also point out that this code will require elevated permissions to open the key for write access. But I believe it ensures that unelevated reads using keys opened in this fashion will only come from the 64-bit view of the registry.
To date, there is no C# or C API to set the registry key flags.
I assume the safest way is to launch the REG.exe command line tool using CreateProcess.
But, for the record, I have pasted some 'C' code from this blog which demonstrates another way using an undocumented API:
typedef enum _CONTROL_FLAGS {
RegKeyClearFlags = 0,
RegKeyDontVirtualize = 2,
RegKeyDontSilentFail = 4,
RegKeyRecurseFlag = 8
} CONTROL_FLAGS;
typedef struct _KEY_CONTROL_FLAGS_INFORMATION {
ULONG ControlFlags;
} KEY_CONTROL_FLAGS_INFORMATION, *PKEY_CONTROL_FLAGS_INFORMATION;
typedef enum _KEY_SET_INFORMATION_CLASS {
KeyWriteTimeInformation,
KeyWow64FlagsInformation,
KeyControlFlagsInformation,
KeySetVirtualizationInformation,
KeySetDebugInformation,
MaxKeySetInfoClass // MaxKeySetInfoClass should always be the last enum
} KEY_SET_INFORMATION_CLASS;
NTSYSAPI NTSTATUS NTAPI NtSetInformationKey(
IN HANDLE KeyHandle,
IN KEY_SET_INFORMATION_CLASS InformationClass,
IN PVOID KeyInformationData,
IN ULONG DataLength );
typedef NTSYSAPI NTSTATUS (NTAPI* FuncNtSetInformationKey) (
HANDLE KeyHandle,
KEY_SET_INFORMATION_CLASS InformationClass,
PVOID KeyInformationData,
ULONG DataLength );
BOOL CRegLonMigration::SetDontVirtualizeFlag(LPCTSTR keyPath)
{
FuncNtSetInformationKey ntsik = (FuncNtSetInformationKey)GetProcAddress(GetModuleHandle( _T("ntdll.dll") ), "NtSetInformationKey" );
KEY_CONTROL_FLAGS_INFORMATION kcfi = {0};
kcfi.ControlFlags = RegKeyDontVirtualize | RegKeyRecurseFlag;
HKEY hKey = NULL;
LSTATUS status;
if (ERROR_SUCCESS == (status = ::RegOpenKeyEx(ROOT_KEY, keyPath, 0, KEY_ALL_ACCESS, &hKey)))
{
NTSTATUS status = ntsik( hKey, KeyControlFlagsInformation, &kcfi, sizeof( KEY_CONTROL_FLAGS_INFORMATION ) );
RegCloseKey( hKey );
return TRUE;
}
return FALSE;
}