How to find the path of the "Shared Documents" folder from c#? - c#

In Windows Vista, the special folder "Shared Documents" contains documents accessible by all the users in the machine. That folder was renamed to "Public Documents" in Windows 7.
How can I find its physical path from c#?
Note that Environment.GetFolderPath(Environment.SpecialFolder.xxx) doesn't have the folder I'm looking for.

The SpecialFolder enum has had a large, and long overdue, update in .NET 4 - one of the new additions is the CommonDocuments member.

What about this?
[DllImport("shell32.dll")]
static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken,
uint dwFlags, [Out] StringBuilder pszPath);
public string GetCommonDocumentsFolder()
{
int SIDL_COMMON_DOCUMENTS = 0x002e;
StringBuilder sb = new StringBuilder();
SHGetFolderPath(IntPtr.Zero,SIDL_COMMON_DOCUMENTS,IntPtr.Zero,0x0000,sb);
return SB.ToString();
}
Answer courtesy of, er..., expert-exchange that we all love to hate.

Path.Combine(Environment.GetEnvironmentVariable("PUBLIC"), "Documents");

Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);
The file system directory that contains documents that are common to all users. This special folder is valid for Windows NT systems, Windows 95, and Windows 98 systems with Shfolder.dll installed.

Look in ShlObj.h for more CSIDLs
interestingly enough SHGetFolderPath has been depricated...
See http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx

Related

How do I get the name of the current executable in C#? (.NET 5 edition)

How do I get the name the executable was invoked as (equivalent to C's argv[0])? I actually need to handle somebody renaming the executable and stuff like that.
There's a famous question with lots of answers that don't work. Answers tried:
System.AppDomain.CurrentDomain.FriendlyName
returns the name it was compiled as
System.Diagnostics.Process.GetCurrentProcess().ProcessName
strips extension (ever rename a .exe to a .com?), also sees through symbolic links
Environment.GetCommandLineArgs()[0]
It returns a name ending in .dll, clearly an error.
Assembly.GetEntryAssembly().Location
Returns null
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
Returns a .dll name again.
The documentation for .net 5.0 says Environment.GetCommandLineArguments()[0] works; however it doesn't actually work. It somehow sees through symbolic links and returns the real executable name.
What I'm trying to do is link all of our stuff into a single multi-call binary so I can use the .net 5 framework reducer on the resulting binary so I don't have to ship about 30MB of .net 5 framework we're not using. I really don't want to do a platform ladder and P/Invoke a bunch of stuff unless I have to.
I'm after argv[0] directly, not the running process executable name. In the case of symbolic links, these differ.
Came across this with .NET 6, where Process.GetCurrentProcess().MainModule?.FileName seems to be working fine now, and there's also Environment.ProcessPath.
If targeting Windows only, it might be safer (more predictable) to use interop. Below are some options, including the native GetModuleFileName and GetCommandLine:
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("Process.GetCurrentProcess().MainModule?.FileName");
Console.WriteLine(Process.GetCurrentProcess().MainModule?.FileName);
Console.WriteLine();
Console.WriteLine("Assembly.GetExecutingAssembly().Location");
Console.WriteLine(Assembly.GetExecutingAssembly().Location);
Console.WriteLine();
Console.WriteLine("Environment.ProcessPath");
Console.WriteLine(Environment.ProcessPath);
Console.WriteLine();
Console.WriteLine("Environment.CommandLine");
Console.WriteLine(Environment.CommandLine);
Console.WriteLine();
Console.WriteLine("Environment.GetCommandLineArgs()[0]");
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
Console.WriteLine();
Console.WriteLine("Win32.GetProcessPath()");
Console.WriteLine(Win32.GetProcessPath());
Console.WriteLine();
Console.WriteLine("Win32.GetProcessCommandLine()");
Console.WriteLine(Win32.GetProcessCommandLine());
Console.WriteLine();
public static class Win32
{
private const int MAX_PATH = 260;
private const int INSUFFICIENT_BUFFER = 0x007A;
private const int MAX_UNICODESTRING_LEN = short.MaxValue;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr GetCommandLine();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
private static extern int GetModuleFileName(
IntPtr hModule, StringBuilder lpFilename, [MarshalAs(UnmanagedType.U4)] int nSize);
public static string GetProcessCommandLine()
{
return Marshal.PtrToStringUni(GetCommandLine()) ??
throw new Win32Exception(nameof(GetCommandLine));
}
public static string GetProcessPath()
{
var buffer = new StringBuilder(MAX_PATH);
while (true)
{
int size = GetModuleFileName(IntPtr.Zero, buffer, buffer.Capacity);
if (size == 0)
{
throw new Win32Exception();
}
if (size == buffer.Capacity)
{
// double the buffer size and try again.
buffer.EnsureCapacity(buffer.Capacity * 2);
continue;
}
return Path.GetFullPath(buffer.ToString());
}
}
}
The output when running via dotnet run:
Process.GetCurrentProcess().MainModule?.FileName
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Assembly.GetExecutingAssembly().Location
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.ProcessPath
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Environment.CommandLine
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.GetCommandLineArgs()[0]
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Win32.GetProcessPath()
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Win32.GetProcessCommandLine()
"C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe"
Oh, and for a Windows Forms application, there always has been Application.ExecutablePath.
Updated, running it on Ubuntu 22.04 with .NET 6.0.2 (with Win32 interop removed), either via dotnet run or directly as ./ProcessPath:
Process.GetCurrentProcess().MainModule?.FileName
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Assembly.GetExecutingAssembly().Location
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.ProcessPath
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Environment.CommandLine
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.GetCommandLineArgs()[0]
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
After watching everything fail, it became necessary to P/Invoke stuff to make this work. While Process.GetCurrentProcess().MainModule?.FileName reliably returns the executable binary (at least when not running under the debugger), this does not provide the command invocation the binary was launched with.
On Windows, GetCommandLine() is P/Invokable and needs only some parsing to get the information. On *n?x, reading /proc/self/cmdline does the same job.
I built a library encapsulating this. https://github.com/joshudson/Emet/tree/master/MultiCall You can find binaries on nuget.org ready to go.
I should have self-answered a long time ago. Better late than never. Nobody seemed to care until now.
Using .NET Core 3.1, this worked for me to restart a currently running program.
string filename = Process.GetCurrentProcess().MainModule.FileName;
System.Diagnostics.Process.Start(filename);
// Closes the current process
Environment.Exit(0);

How to remove/replace fonts currently in use

I'd like to deploy font packages programmatically during our update process. Therefore, I need to replace the old font files in \Windows\Fonts with the new ones extracted from a ZIP archive. Deleting an existing font file will cause an IOException, because of another process accessing the file.
I tried to release the font by calling RemoveFontResource and propagating the change with a WM_FONTCHANGE message, but no luck. RemoveFontResource returns false with native error 2 (file not found?) and HRESULT -2147467259. The file actually does exist.
Sample code:
[DllImport("coredll.dll", SetLastError = true)]
private static extern int RemoveFontResource(string lpName);
[DllImport("coredll.dll", SetLastError = true)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private const int WM_FONTCHANGE = 0x001D;
private const int HWND_BROADCAST = 0xffff;
public const string SAMPLE_FILE = #"\Windows\Fonts\MyFont.ttf";
public void RemoveFont()
{
if (!RemoveFontResource(SAMPLE_FILE))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
SendMessage(new IntPtr(HWND_BROADCAST), WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
File.Delete(SAMPLE_FILE);
}
Is there actually a clean way to deploy and replace fonts system-wide?
If the font files are part of the OS image, they have the eXecute-In-Place (XIP) attribute set (read-only, system, ROM) and cannot simply replaced. Although one can copy a file of the same name on top, which hides the original file. If the file copied on top is deleted, the original file is back. This is the case with all XIP files.
I recommend you use the SysCache dir to replace the files. This will work with all file replacements, regardless if they are XIP, drivers, DLLs, in-use or otherwise locked for replacement.
If you place files with the same name as the origibal file in the syscache dir, these will be used instead of the original files. But you have to warmboot the device after placing files in the syscache dir. Windows CE/Mobile will recognize and 'use' the syscache files only during the OS startup.
The location of the syscache dir may vary, depending on OS version and OEM decisions. On WM 6 devices it should be \Windows\System\syscache. Check the file system of the device for an existing syscache directory.
BTW: font files do not need to be placed into \Windows\Fonts, it is OK to place them in \Windows.

How do you get the Default Users folder (e.g. c:\users\Default)

I've looked at the Environment.GetFolderPath method and the System.Environment.SpecialFolder enum but I couldn't see anything that returns the path of the Default Users folder.
Can someone please tell me how to get the Default Users folder (or even better the Default Users AppData Local folder path e.g. c:\users\Default\AppData\Local) programmatically as I need to copy some files into this folder?
Thank you
There are lots of articles on the web that describe how to change the Default User Profile path:
http://support.microsoft.com/kb/214636
http://www.nextofwindows.com/how-to-change-user-profile-default-location-in-windows-7/
They all say the current Default Profile Path is stored in the following registry location:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
e.g.
%SystemDrive%\Users\Default
And I found this page to get the System Drive:
How to get current windows directory e.g. C:\ in C#
Path.GetPathRoot(Environment.SystemDirectory)
So I'm going to use that. Thanks for your help.
UPDATE
I've just tried the following code and it returns C:\Users\Default. So there is no need to replace the %SystemDrive% text stored in the registry key. It replaces it automatically.
using (RegistryKey profileListKey = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"))
{
string defaultPath = profileListKey.GetValue("Default").ToString();
}
Snippet from LINQPad (Language: C# Program) that outputs 'C:\Users\Default\Desktop':
void Main()
{
GetFolderPath(Environment.SpecialFolder.Desktop).Dump();
}
// Define other methods and classes here
[DllImport("shfolder.dll", CharSet=CharSet.Auto)]
internal static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, int hToken, int dwFlags, StringBuilder lpszPath);
public static string GetFolderPath(Environment.SpecialFolder folder)
{
if (!Enum.IsDefined(typeof(Environment.SpecialFolder), folder))
{
throw new Exception("Crap");
}
StringBuilder lpszPath = new StringBuilder(260);
SHGetFolderPath(IntPtr.Zero, (int) folder, -1, 0, lpszPath);
string path = lpszPath.ToString();
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, path).Demand();
return path;
}
Edit: I had the following imports in LINQPad
System.Runtime.InteropServices
System.Globalization
System.Security.Permissions
I used reflector to look at Environment.GetFolderPath and then took a look at SHGetFolderPath that specifies by passing -1 as hToken you get Default User instead.
You can't because the access to that folder is denied, this folder is used only by the Microsoft. I sure Environment or any other class will not provide you such functionality. This can only be done with some sort of hacking maybe?

Accessing Patch Information?

Does anyone know, how given a GUID that identifies an installed product, you can find the patches installed for that product with C#?
The application is quite complex and from time to time, we create patches (MSP files) through Orca/MSI. These patches can then be installed on the customer's computer and can then be viewed in "View Installed Updates" under Programs and Features.
I've tried two approaches:
Using WMI I can find my product in Win32_Product and retrieve the
information there. However, if I then query either
Win32_PatchPackage or Win32_Patch for matches against the
"ProductCode". I would have expected the captions/description to
contain the information I want, but all I get is another separate
set of GUIDs for each which doesn't seem very obvious what to do
with it.
Similarly, using the Registry I can find the Product (under
HKLM\Software\Microsoft\Uninstall\\, and with some digging I
can find Patches (under
HKLM\Software\Microsoft\Installer\UserData\S-1-5-18\Products\)
but the key isn't obvious. It isn't the same as my products
installer GUID.
This question discusses similar issues, but the questioner was looking for the Windows patches, while I need my own applicaitons patches - so there solution doesn't really work for me.
Thanks in advance.
I was able to achieve this by plugging the ProductCode returned from Win32_PatchPackage into a Win32 dll and then using as so.
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
internal static extern Int32 MsiGetPatchInfoEx(string szPatchCode, string szProductCode, string szUserSid, int dwContext, string szProperty, [Out] StringBuilder lpValue, ref Int32 pcchValue);
// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa370128%28v=vs.85%29.aspx
// for valid values for the property paramater
private static string getPatchInfoProperty(string patchCode, string productCode, string property)
{
StringBuilder output = new StringBuilder(512);
int len = 512;
MsiGetPatchInfoEx(patchCode, productCode, null, 4, property, output, ref len);
return output.ToString();
}
public static string GetPatchDisplayName(string patchCode, string productCode)
{
return getPatchInfoProperty(patchCode, productCode, "DisplayName");
}

Why can't I get GetPrivateProfileString to work via pinvoke?

Running a c# console app I wrote on 64 bit Vista. Here's the code:
class Class1
{
static void Main(string[] args)
{
Debug.Assert(File.Exists(#"c:\test.ini"));
StringBuilder sb = new StringBuilder(500);
uint res = GetPrivateProfileString("AppName", "KeyName", "", sb, sb.Capacity, #"c:\test.ini");
Console.WriteLine(sb.ToString());
}
[DllImport("kernel32.dll")]
static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName);
}
I'm sure I'll get a big "DUH!" for an answer, but I'm not seeing why this fails to work. Other than the Debug.Assert, this code was cut from the c# sample at this page
This one has been busting my chops all day, too. I think I found a work-around, which I really don't want to have to deal with: insert a blank line before the first section header in your .ini file. Now run your app and see if you don't start seeing the values you were expecting.
Considering this bug has apparently been around for years, I'm surprised MS hasn't fixed it by now. But then, .ini files were supposed to have gone away years ago. Which of course is funny, because there are a lot of places MS uses .ini file (e.g, desktop.ini). But I think MS is aware of the bug, because I notice my desktop.ini files include a leading blank line. Hmmm...
The main thing I see is that you should be passing in an uint for nSize, as well as the return value. This is because the return and nSize parameters of GetPrivateProfileString are DWORD values, which are unsigned 32bit integers.
I personally have used the syntax on PInvoke.net:
[DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);
In addition, you'll need to put the full path to the file in place, unless the file is located in the Windows directory. From the docs:
If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.
According to pinvoke.net, nSize should be a UINT. Also they are using an absolute path in their example.
Other than those differences, I can't see anything else.
Providing it's throwing a invalid format exception, try setting target platform to x86 to solve the problem.
Usage example from pinvoke.net is
[DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder(500);
uint res = GetPrivateProfileString("AppName", "KeyName", "", sb, (uint)sb.Capacity, #"c:\test.ini");
Console.WriteLine(sb.ToString());
}
If no path is specified, GetPrivateProfileString will look for Test.ini in the Windows directory.
Old APIs like GetPrivateProfileString don't handle Unicode well (even though there's a GetPrivateProfileStringW function). If Test.ini contains a UTF header or Unicode characters, that might be enough to prevent GetPrivateProfileString from working correctly.
Also, Vista's UAC can make handling files that are in "special" places tricky (C:\, C:\Windows, C:\Program Files, etc.). Try putting Test.ini in a folder rather than in the root of the C: drive, or turn off UAC. There's a thread on CodeProject that discusses GetPrivateProfileString failing silently when trying to read an .ini from a folder controlled by UAC.
Maybe you should look into looking at an open source solution that will do exactly that without you worrying about p/Invoke's. The project targetted for .NET is called nini I am using this in a project I am working on and I recommend it.
Hope this helps,
Best regards,
Tom.
Can you verify the contents of your test.ini file? Given all of the steps that you've tried, I'm beginning to suspect that your data file is not formatted correctly (misspelling, etc.) In other words, GetPrivateProfileString may be "working", but just not finding your string. Based on the code that you posted, your test.ini file should look something like this:
[AppName]
KeyName=foo

Categories