I want to delete a file stored in an USB flashdrive (actually an android device's sd card).
I ask the user to point the app's folder inside the sd card, and i hold a Shell32.Folder object pointing to it.
I can iterate between the files (FolderItem objects), but how can i delete a file using Shell32 classes?
Usual File.Delete(filePath) does not work, since the FolderItem.Path is a BSTR data type, so maybe the key is to convert from one type to another, but i couldn't find a way to do this.
Any ideas?
EDIT 1:
FolderItem.Path data:
"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_04e8&pid_6860&ms_comp_mtp>-p5100#7&392be4e4&0&0000#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{10001,SECZ9519043CHOHB,12530364416}\{015C008D-013D-0145-D300-D300CB009500}\{015C00EE-013D-0145-0201-37012C010901}\{025901D2-029D-020F-DE01-FE010D02A601}"
This is not a valid path for File.Delete. Any ideas on how to convert that?
EDIT 2:
Method that opens the browse folder window, so the user can point out the app directory, inside his android device's SD card, so i can iterate with folders and files, and sync some data with the server. Done this way due to problems between Win8 and Shell32:
public Shell32.Folder GetShell32NameSpace(Object folder)
{
Type shellAppType = Type.GetTypeFromProgID("Shell.Application");
Object shell = Activator.CreateInstance(shellAppType);
return (Shell32.Folder)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { folder });
}
Using the method above, to get the Folder:
IntPtr windowHandle = new WindowInteropHelper(this).Handle;
Folder androidPath = BrowseForFolder(windowHandle.ToInt32(), "App's data folder", 0, 0);
I create and copy a file into the app's 'data' folder, so the device can recognize a syncing action, tell the user, and close the app:
byte[] data = new UTF8Encoding(true).GetBytes("sync_cable");
if (androidPath != null)
{
foreach (FolderItem dir in androidPath.Items())
{
if (dir.IsFolder && dir.Name == "data")
{
Folder dataFolder = GetShell32NameSpace(dir.Path);
if (dataFolder != null)
{
string tempPath = Path.GetTempPath();
string path = Path.Combine(tempPath, flagFileName);
File.WriteAllBytes(path, data);
Folder localPath = GetShell32NameSpace(tempPath);
if (localPath != null)
{
foreach (FolderItem file in localPath.Items())
{
if (file.Name.Contains(flagFileName))
{
dataFolder.CopyHere(file);
break;
}
}
}
}
break;
}
}
}
After the sync, i want to delete the file, so the app can function normally:
foreach (FolderItem file in dataFolder.Items())
{
if (file.Name.Contains(flagFileName))
{
// THIS THROWS AN 'INVALID PATH' EXCEPTION
// FileInfo fi = new FileInfo(file.Path);
// fi.Delete();
// THIS ALSO THROWS AN 'INVALID PATH' EXCEPTION
// File.Delete(file.Path);
break;
}
}
As mentioned in EDIT 1, file.Path of the file i want to delete, has the following format:
"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_04e8&pid_6860&ms_comp_mtp>-p5100#7&392be4e4&0&0000#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{10001,SECZ9519043CHOHB,12530364416}\{015C008D-013D-0145-D300-D300CB009500}\{015C00EE-013D-0145-0201-37012C010901}\{025901D2-029D-020F-DE01-FE010D02A601}"
EDIT 3:
As suggested here, i tried using P/Invoke to delete the file, with no success.
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteFile(string lpFileName);
Calling the method:
foreach (FolderItem file in dataFolder.Items())
{
if (file.Name.Contains(flagFileName))
{
// ...
bool deleted = DeleteFile(file.Path);
// ...
}
}
The method returns false, and the file is not deleted. I looked in the Event Viewer for a clue about what happened, but found nothing.
The part of the path which looks like "\?\" tells you that it's a long path. Sadly the CLR cannot handle long paths (it's limited to MAX_PATH = 260 nonsense). I believe you can use the WinAPI's DeleteFile to accomplish what you want.
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363915(v=vs.85).aspx
Use PInvoke and you'll be all set.
http://pinvoke.net/default.aspx/kernel32/DeleteFile.html?diff=y
The guid prefix ("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}") is the standard junction point for MyComputer (see https://msdn.microsoft.com/en-us/library/windows/desktop/cc144096(v=vs.85).aspx).
I suspect the file cannot be deleted using such methods as the shell API DeleteFile because it is not in the file system (you can check it using FolderItem.IsFileSystem) because the android device is connected using MTP.
I have been able to delete such files using FolderItem.InvokeVerb("Delete"). This produces a confirmation dialog which can be dismissed using some version of SendKeys (for example freeware Nirsoft's Nircmd.exe). The following is a VBA sample:
Private Sub SampleDelete()
Const sFolder = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_054c&pid_04cb#10fbd475671683#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{10001,00000000000000000000000005671683,7513636864}\{00000007-0000-0000-0000-000000000000}\{000057F1-0000-0000-66A5-884D99020600}"
Const sFileName = "test.txt"
' requires reference to Shell32.dll
Dim oShell As New Shell32.Shell
Dim oFolder As Shell32.Folder
Dim oFile As Shell32.FolderItem
Set oFolder = oShell.Namespace(sFolder)
If Not oFolder Is Nothing Then
Set oFile = oFolder.ParseName(sFileName)
If Not oFile Is Nothing Then
Debug.Print "File system? " & oFile.IsFileSystem
' dismiss the confirmation dialog
SendKeys "~"
' Alternatively use an external function, like this freeware utility
' Shell "nircmd.exe sendkey enter press", vbHide
oFile.InvokeVerb "Delete"
Else
Debug.Print "File not found"
End If
Else
Debug.Print "Folder not found"
End If
Set oShell = Nothing
Set oFolder = Nothing
Set oFile = Nothing
End Sub
Related
I'm writing a program that needs to read in files from anywhere on the system. Some users of the program have paths over the 260 character limit. The OpenFileDialog doesn't work with files with paths over 260 characters.
I've tried using both System.Windows.Forms.OpenFileDialog and Microsoft.Win32.OpenFileDialog. In the case of the former, when I click "open" after I've navigated to and selected the file, the window doesn't close and the program doesn't continue. In the case of the latter, the window will close when I click "open", but the path is an empty string.
I've updated the registry on my computer. I've edited the application manifest file. I would try to prepend the "//?/" string to my paths, but there are no paths to which to prepend.
var dialog = new OpenFileDialog
{
// initialize dialog
}
if (dialog.ShowDialog() == DialogResult.OK) // DialogResult.OK replaced with true if using Microsoft.Win32.OpenFileDialog
{
// if when using System.Windows.Forms.OpenFileDialog, I will never get to this point
// if using Microsoft.Win32.OpenFileDialog, I will get here but dialog.FileNames will be empty
}
I'd expect the above code to work the same with long and short paths if I've updated the registry and the app manifest. I'm suspecting that this just isn't supported, but all of my searches have revealed people providing a solution that either doesn't work or only works in specific cases.
In the case of System.Windows.Forms.OpenFileDialog I was able to get this to work by setting ValidateNames to false to overcome ShowDialog() not returning when the user clicks "open",
System.Windows.Forms.OpenFileDialog openFileDialog_WindowsForms = new System.Windows.Forms.OpenFileDialog
{
CheckFileExists = true,
CheckPathExists = true,
ValidateNames = false // this will allow paths over 260 characters
};
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
string[] fileNames = openFileDialog_WindowsForms.getFileNames_WindowsForms();
foreach (var file in fileNames)
{
try
{
Console.WriteLine(File.ReadAllText(file));
}
catch (Exception ex)
{
Console.WriteLine("Couldn't open file from Windows.Forms.OpenFileDialog:" + ex.Message);
}
}
};
and reflection to overcome the paths not being accessible from the FilePath or FilePaths properties. It turns out the paths were present in a private property which I could access using reflection.
public static class OpenFileDialogLongPathExtension
{
public static string[] getFileNames_WindowsForms(this System.Windows.Forms.OpenFileDialog dialog)
{
var fileNamesProperty = dialog.GetType().GetProperty("FileNamesInternal", BindingFlags.NonPublic | BindingFlags.Instance);
var fileNamesFromProperty = (string[])fileNamesProperty?.GetValue(dialog);
return fileNamesFromProperty;
}
}
I tried something similar for the Microsoft.Win32.OpenFileDialog, but it seems like that private property is still invalid, so the same solution wouldn't work.
Either way, I hope this helps someone else. This example was created using .NET Framework 4.8.
My application creates files and directories throughout the year and needs to access the timestamps of those directories to determine if it's time to create another one. So it's vital that when I move a directory I preserve its timestamps. I can do it like this when Directory.Move() isn't an option (e.g. when moving to a different drive).
FileSystem.CopyDirectory(sourcePath, targetPath, overwrite);
Directory.SetCreationTimeUtc (targetPath, Directory.GetCreationTimeUtc (sourcePath));
Directory.SetLastAccessTimeUtc(targetPath, Directory.GetLastAccessTimeUtc(sourcePath));
Directory.SetLastWriteTimeUtc (targetPath, Directory.GetLastWriteTimeUtc (sourcePath));
Directory.Delete(sourcePath, true);
However, all three of these "Directory.Set" methods fail if File Explorer is open, and it seems that it doesn't even matter whether the directory in question is currently visible in File Explorer or not (EDIT: I suspect this has something to do with Quick Access, but the reason isn't particularly important). It throws an IOException that says "The process cannot access the file 'C:\MyFolder' because it is being used by another process."
How should I handle this? Is there an alternative way to modify a timestamp that doesn't throw an error when File Explorer is open? Should I automatically close File Explorer? Or if my application simply needs to fail, then I'd like to fail before any file operations take place. Is there a way to determine ahead of time if Directory.SetCreationTimeUtc() for example will encounter an IOException?
Thanks in advance.
EDIT: I've made a discovery. Here's some sample code you can use to try recreating the problem:
using System;
using System.IO;
namespace CreationTimeTest
{
class Program
{
static void Main( string[] args )
{
try
{
DirectoryInfo di = new DirectoryInfo( #"C:\Test" );
di.CreationTimeUtc = DateTime.UtcNow;
Console.WriteLine( di.FullName + " creation time set to " + di.CreationTimeUtc );
}
catch ( Exception ex )
{
Console.WriteLine( ex );
//throw;
}
finally
{
Console.ReadKey( true );
}
}
}
}
Create C:\Test, build CreationTimeTest.exe, and run it.
I've found that the "used by another process" error doesn't always occur just because File Explorer is open. It occurs if the folder C:\Test had been visible because C:\ was expanded. This means the time stamp can be set just fine if File Explorer is open and C:\ was never expanded. However, once C:\Test becomes visible in File Explorer, it seems to remember that folder and not allow any time stamp modification even after C:\ is collapsed. Can anyone recreate this?
EDIT: I'm now thinking that this is a File Explorer bug.
I have recreated this behavior using CreationTimeTest on multiple Windows 10 devices. There are two ways an attempt to set the creation time can throw the "used by another process" exception. The first is to have C:\Test open in the main pane, but in that case you can navigate away from C:\Test and then the program will run successfully again. But the second way is to have C:\Test visible in the navigation pane, i.e. to have C:\ expanded. And once you've done that, it seems File Explorer keeps a handle open because the program continues to fail even once you collapse C:\ until you close File Explorer.
I was mistaken earlier. Having C:\Test be visible doesn't cause the problem. C:\Test can be visible in the main pane without issue. Its visibility in the navigation pane is what matters.
Try this:
string sourcePath = "";
string targetPath = "";
DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
FileSystem.CopyDirectory(sourcePath, targetPath, overwrite);
DirectoryInfo targetDirectory = new DirectoryInfo(targetPath);
targetDirectory.CreationTimeUtc = sourceDirectoryInfo.CreationTimeUtc;
targetDirectory.LastAccessTimeUtc = sourceDirectoryInfo.LastAccessTimeUtc;
targetDirectory.LastWriteTimeUtc = sourceDirectoryInfo.LastWriteTimeUtc;
Directory.Delete(sourcePath, true);
This will allow you to set the creation/access/write times for the target directory, so long as the directory itself is not open in explorer (I am assuming it won't be, as it has only just been created).
I am suspecting FileSystem.CopyDirectory ties into Explorer and somehow blocks the directory. Try copying all the files and directories using standard C# methods, like this:
DirectoryCopy(#"C:\SourceDirectory", #"D:\DestinationDirectory", true);
Using these utility methods:
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
if (!dir.Exists)
{
throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName);
}
if ((dir.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
// Don't copy symbolic links
return;
}
var createdDirectory = false;
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
var newdir = Directory.CreateDirectory(destDirName);
createdDirectory = true;
}
// Get the files in the directory and copy them to the new location.
DirectoryInfo[] dirs = dir.GetDirectories();
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
if ((file.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
continue; // Don't copy symbolic links
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
CopyMetaData(file, new FileInfo(temppath));
}
// If copying subdirectories, copy them and their contents to new location.
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
if (createdDirectory)
{
// We must set it AFTER copying all files in the directory - otherwise the timestamp gets updated to Now.
CopyMetaData(dir, new DirectoryInfo(destDirName));
}
}
private static void CopyMetaData(FileSystemInfo source, FileSystemInfo dest)
{
dest.Attributes = source.Attributes;
dest.CreationTimeUtc = source.CreationTimeUtc;
dest.LastAccessTimeUtc = source.LastAccessTimeUtc;
dest.LastWriteTimeUtc = source.LastWriteTimeUtc;
}
I created a button that grabs the text contents of the Clipboard class, checks if it's a folder path and creates a hyperlink out of it. The user will probably just copy the path from the explorer window. The problem I have, which seems to be the opposite of a lot of questions I found, is that I want the drive path (T:\Natzely) instead of the UNC path (\SEOMAFIL02\Trash\Natzely).
When I ctrl+v the copied path into either Word, Notepad or Outlook it gets copied as drive path, but when I copy it to Chrome's address bar or try to retrieve it from the Clipboard class it gets copied as the UNC path. How does Microsoft deal with this?
My drive letters stay pretty much static, so I don't have to worry about the T drive not being the Trash drive in the future.
EDIT
Here is the code
object clipboardText = Clipboard.GetText();
if(!Directory.Exists(clipboardText.ToString()))
{
//Show error message
}
doc = GetDoc(); //Get document to add the hyperlink to
sel = doc.Selection;
range = sel.Range;
hyperlinks = sel.Hyperlinks;
hyperlinks.Add(range, ref clipboardText);
EDIT Numero Dos
It seems to be more of an issue with Hyperlinks.Add than the clipboard. If I add a space before the clipboard text object clipboardText = " " + Clipboard.GetText() it seems to correct the issue, the hyperlink will now have and extra space before but the link still works.
Have you see this question from Microsoft?
Translate UNC path to local mounted driveletter.
Seems like there are two ways. The first way is to get the value from the registry:
string UNCtoMappedDrive(string uncPath)
{
Microsoft.Win32.RegistryKey rootKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("network");
foreach (string subKey in rootKey.GetSubKeyNames())
{
Microsoft.Win32.RegistryKey mappedDriveKey = rootKey.OpenSubKey(subKey);
if (string.Compare((string)mappedDriveKey.GetValue("RemotePath", ""), uncPath, true) == 0)
return subKey.ToUpperInvariant() + #":\";
}
return uncPath;
}
The other way is with System.Management. The example is large, but here's the core of it:
private void LoadDrives()
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT RemoteName, LocalName FROM Win32_NetworkConnection");
List<UncToDrive> drives = new List<UncToDrive>();
foreach (ManagementObject obj in searcher.Get())
{
object localObj = obj["LocalName"];
if (!Object.Equals(localObj, null))
drives.Add(new UncToDrive(obj["RemoteName"].ToString(), localObj.ToString()));
}
}
They search for all paths with the drive (LocalName) and without (RemoteName), then save them in a list for later lookup.
My WPF application needs to list the localized names of all Metro/WinRT applications installed for the user. I created a repo to store a working sample for the code presented: https://github.com/luisrigoni/metro-apps-list
1) Using PackageManager.FindPackagesForUser() method
var userSecurityId = WindowsIdentity.GetCurrent().User.Value;
var packages = packageManager.FindPackagesForUser(userSecurityId);
foreach (var package in packages)
Debug.WriteLine(package.Id.Name);
}
// output:
// Microsoft.BingFinance
// Microsoft.BingMaps
// Microsoft.BingSports
// Microsoft.BingTravel
// Microsoft.BingWeather
// Microsoft.Bing
// Microsoft.Camera
// microsoft.microsoftskydrive
// microsoft.windowscommunicationsapps
// microsoft.windowsphotos
// Microsoft.XboxLIVEGames
// Microsoft.ZuneMusic
// Microsoft.ZuneVideo
These outputs don't seems too friendly to show to the user...
2) Reading the AppxManifest.xml of each of these apps
var userSecurityId = WindowsIdentity.GetCurrent().User.Value;
var packages = packageManager.FindPackagesForUser(userSecurityId);
foreach (var package in packages)
{
var dir = package.InstalledLocation.Path;
var file = Path.Combine(dir, "AppxManifest.xml");
var obj = SerializationExtensions.DeSerializeObject<Package>(file);
if (obj.Applications != null)
{
foreach (var application in obj.Applications)
{
Debug.WriteLine(application.VisualElements.DisplayName);
}
}
}
// output:
// ms-resource:AppTitle
// ms-resource:AppDisplayName
// ms-resource:BingSports
// ms-resource:AppTitle
// ms-resource:AppTitle
// ms-resource:app_name
// ms-resource:manifestDisplayName
// ms-resource:ShortProductName
// ms-resource:mailAppTitle
// ms-resource:chatAppTitle
// ms-resource:///resources/residTitle
// ms-resource:///strings/peopleAppName
// ms-resource:///photo/residAppName
// ms-resource:34150
// ms-resource:33273
// ms-resource:33270
Definitely not friendly...
Update 1) Increasing above item (2) with SHLoadIndirectString funcion (hint by Erik F)
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
private static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
static internal string ExtractStringFromPRIFile(string pathToPRI, string resourceKey)
{
string sWin8ManifestString = string.Format("#{{{0}? {1}}}", pathToPRI, resourceKey);
var outBuff = new StringBuilder(1024);
int result = SHLoadIndirectString(sWin8ManifestString, outBuff, outBuff.Capacity, IntPtr.Zero);
return outBuff.ToString();
}
[...]
foreach (var application in obj.Applications)
{
Uri uri = new Uri(application.VisualElements.DisplayName);
var resourceKey = string.Format("ms-resource://{0}/resources/{1}", package.Id.Name, uri.Segments.Last());
Debug.WriteLine(ExtractStringFromPRIFile("<path/to/pri>", resourceKey));
}
[...]
// output:
// Finance
// Maps
// Sports
// Travel
// Weather
// Bing
// Camera
// SkyDrive
// Mail
// Messaging
// Calendar
// People
// Photos
// Games
// Music
// Video
Much, much better. We already have english labels. But how to extract other language resources?
I'm expecting retrieve the same label that is shown on Start Screen for each app, something like "Finanças", "Esportes", "Clima" if my language is pt-BR; "Finances", "Sports", "Weather" if my language is en-US.
[Q] Is there another way to get the application names? Maybe native/Win32 (DISM API/...)? Is possible to load the .pri file of each app to get the localized name?
As said, an updated working sample is here: https://github.com/luisrigoni/metro-apps-list
Using SHLoadIndirectString, you should be able to construct a fully-qualified reference for Package name and resource ID of the form #{PackageFullName?resource-id}
Documented here:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb759919(v=vs.85).aspx
You'll have to transform the manifest string into the proper form, though. It should be:
ms-resource://PackageName/Resources/Id
PackageName is the name rather than the full name. Resources isn't strictly required but it's the default and it's usually there. I'd try to look up the resource without inserting resources and then try again if that fails.
For example, the camera app has "ms-resource:manifestDisplayName" in the manifest, so first you should try(*):
#{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/manifestAppDescription}
When that fails, insert "resources" and try:
#{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/resources/manifestAppDescription}
That should work. You'll want to try both forms because blindly inserting "resources" will break apps like skydrive, communications and photos which insert the first part of the path directly.
Still a bit of a pain, but better than dumping and parsing gigantic XML files.
(*) "Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe" is taken from an example - you'll obviously want the FullName of the one that's actually present on your system.
Looks like you're stuck with makepri.exe dump /if <prifile>.pri /of <outfile>.xml. Then all you have to do is parse/deserialize the XML file.
In addition to what Erik F told above along with updated question from Luis Rigoni (OP) here are further tips:
I found that path to PRI is better solution that giving package name. Many a times SHLoadIndirectString doesn't resolve the resource when just package name is given. Path to PRI is the package's install location + resources.pri . Example: C:\Program Files\WindowsApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\Resources.pri.
The VisualElements/DisplayName may contain the full url to the resource. If so, you don't have to further format it using package name and 'resources' folder like ms-resource://{0}/resources/{1}. If the DisplayName contains the package name itself, then you can assume that it is a full url.
Like Erik F pointed out, when SHLoadIndirectString fails, try again without the /resources/ folder.
Also sometimes the resources folder itself will be part of VisualElements/DisplayName. Example: ms-resource:///MSWifiResources/AppDisplayName. Also, notice the three ///. Yes, you will have to take care of that. You just have to take MSWifiResources/AppDisplayName and suffix it to ms-resource:///MSWifiResources/AppDisplayName
.
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
//If VisualElements/DisplayName contains ms-resource: then call the below
//function. identity is nothing but package name that can be retrieved from
//Identity/Name element in AppxManifest.xml.
private static string GetName(string installPath, string name, string identity) {
StringBuilder sb = new StringBuilder();
int result;
//if name itself contains the package name then assume full url else
//format the resource url
var resourceKey = (name.ToLower().Contains(identity.ToLower())) ? name : string.Format("ms-resource://{0}/resources/{1}", identity, name.Split(':')[[1]].TrimStart('/'));
string source = string.Format("#{{{0}? {1}}}", Path.Combine(installPath, "resources.pri"), resourceKey);
result = SHLoadIndirectString(source, sb, -1, IntPtr.Zero);
if (result == 0)
return sb.ToString();
//if the above fails then we try the url without /resources/ folder
//because some apps do not place the resources in that resources folder
resourceKey = string.Format("ms-resource://{0}/{1}", identity, name.Split(':')[[1]].TrimStart('/'));
source = string.Format("#{{{0}? {1}}}", Path.Combine(installPath, "resources.pri"), resourceKey);
result = SHLoadIndirectString(source, sb, -1, IntPtr.Zero);
if (result == 0)
return sb.ToString();
return string.Empty;
}
Actually, you can do better than makepri - check out the ResourceIndexer:
http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.resources.management.resourceindexer.aspx
You should be able to give IndexFileContentsAsync a PRI file and get back all of the resource candidates in the file. You'll have to reassemble and reinterpret them, but it will get you all of the possible resource values.
For Windows 8 apps, at least.
For apps which take advantage of resource packages (introduced in Windows 8.1), the resources.pri in the package contains only the defaults. To get the resources for the any other installed languages (or scale factors) you'll need to also index the PRI files from the additional resource packages.
I've got a directory location, how can I create all the directories? e.g. C:\Match\Upload will create both Match and the sub-directory Upload if it doesn't exist.
Using C# 3.0
Thanks
Directory.CreateDirectory(#"C:\Match\Upload") will sort this all out for you. You don't need to create all the subdirectories! The create directory method creates all directories and sub directories for you.
if (!System.IO.Directory.Exists(#"C:\Match\Upload"))
{
System.IO.Directory.CreateDirectory(#"C:\Match\Upload");
}
Here is an example with a DirectoryInfo object that will create the directory and all subdirectories:
var path = #"C:\Foo\Bar";
new System.IO.DirectoryInfo(path).Create();
Calling Create() will not error if the path already exists.
If it is a file path you can do:
var path = #"C:\Foo\Bar\jazzhands.txt";
new System.IO.FileInfo(path).Directory.Create();
for googlers: in pure win32/C++, use SHCreateDirectoryEx
inline void EnsureDirExists(const std::wstring& fullDirPath)
{
HWND hwnd = NULL;
const SECURITY_ATTRIBUTES *psa = NULL;
int retval = SHCreateDirectoryEx(hwnd, fullDirPath.c_str(), psa);
if (retval == ERROR_SUCCESS || retval == ERROR_FILE_EXISTS || retval == ERROR_ALREADY_EXISTS)
return; //success
throw boost::str(boost::wformat(L"Error accessing directory path: %1%; win32 error code: %2%")
% fullDirPath
% boost::lexical_cast<std::wstring>(retval));
//TODO *djg* must do error handling here, this can fail for permissions and that sort of thing
}