IconPath not exsist - c#

When I'm searching for a IsSystemSoundsSession I can't find the IconPath provided. On my system (Windows 10 1909). I'm getting IconPath #%SystemRoot%\\System32\\AudioSrv.Dll,-203 This file doesn't exist. (yes I expanded the variable for this, but that resolves to #C:\Windows\System32\AudioSrv.dll (notice the '#').
Found a solution. The file that I'm searching for is located in C:\Windows\System32\AudioSrv.dll. Notice how there is an # part as first character? It looks like that is the problem. Not sure if this is a problem with NAudio or Windows API.
Code to retrieve the IconPath
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active);
foreach (var device in devices)
{
var sessions = device.AudioSessionManager.Sessions;
for (int i = 0; i < sessions.Count; i++)
{
var session = sessions[i];
var iconPath = session.IconPath;
// iconPath == '#%SystemRoot%\\System32\\AudioSrv.Dll,-203'
}
}
}

You could hardcode c:\windows\system32 instead, but actually you don't really know that location. That is where the environment variable %systemroot% comes in.
Your # string is a path name containing this environment variable. You cannot open it as a file, first the variable need be resolved, consider:
string trueIconPath = Environment.ExpandEnvironmentVariables( iconPath.Substring(1))
The outcome will probably be: c:\windows\system32\AudioSrv.dll as intended for your machine.. on my PC it would become f:\cfg1\Windows\System32\AudioSrv.dll
Refer for more info: https://learn.microsoft.com/en-us/dotnet/api/system.environment.expandenvironmentvariables?view=netcore-3.1

Related

C# - Shell32.NameSpace does not work when trying to extract metadata from files

I am attempting to get the metadata from a few music files and failing miserably. Online, there seems to be absolutely NO HOPE in finding an answer; no matter what I google. I thought it would be a great time to come and ask here because of this.
The specific error I got was: Error HRESULT E_FAIL has been returned from a call to a COM component. I really wish I could elaborate on this issue, but I'm simply getting nothing back from the COMException object. The error code was -2147467259, and it in hex is -0x7FFFBFFB, and Microsoft have not documented this specific error.
I 70% sure that its not the file's fault. My code will run through a directory full of music and convert the file into a song, hence the ConvertFileToSong name. The function would not be running if the file were to not exist is what I'm trying to say.
The only thing I can really say is that I'm using Dotnet 6, and have a massive headache.
Well, I guess I could also share another problem I had before this error showed up. Dotnet6 has top level code or whatever its called, this means that I can't add the [STAThread] attribute. To solve this, I simply added the code bellow to the top. Not sure why I have to set it to unknown, but that's what I (someone else on Stack Overflow) have to do. That solved that previous problem that the Shell32 could not start, but could that be causing my current problem? Who knows... definitely not me.
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
Here is the code:
// Help from: https://stackoverflow.com/questions/37869388/how-to-read-extended-file-properties-file-metadata
public static Song ConvertFileToSong(FileInfo file)
{
Song song = new Song();
List<string> headers = new List<string>();
// initialise the windows shell to parse attributes from
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder = null;
try
{
objFolder = shell.NameSpace(file.FullName);
}
catch (COMException e)
{
int code = e.ErrorCode;
string hex = code.ToString();
Console.WriteLine("MESSAGE: " + e.Message + ", CODE: " + hex);
return null;
}
Shell32.FolderItem folderItem = objFolder.ParseName(file.Name);
// the rest of the code is not important, but I'll leave it there anyway
// pretty much loop infinetly with a counter better than
// while loop because we don't have to declare an int on a new
// line
for (int i = 0; i < short.MaxValue; i++)
{
string header = objFolder.GetDetailsOf(null, i);
// the header does not exist, so we must exit
if (String.IsNullOrEmpty(header)) break;
headers.Add(header);
}
// Once the code works, I'll try and get this to work
song.Title = objFolder.GetDetailsOf(folderItem, 0);
return song;
}
Good night,
Diseased Finger
Ok, so the solution isn't that hard. I used file.FullName which includes the file's name, but Shell32.NameSpace ONLY requires the directory name (discluding the file name).
This is the code that fixed it:
public static Song ConvertFileToSong(FileInfo file)
{
// .....
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder objFolder = file.DirectoryName;
Shell32.FolderItem folderItem = objFolder.ParseName(file.Name);
// .....
return something;
}

Finding Network Drive from UNC Path

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.

.Net method that mimics windows default file rename behavior

In Windows Explorer, if you copy a file and the file name already exists and you choose not to override the file, Windows Explorer uses a specific file rename algorithm, i.e. it tries to append something like "copy", if this file exists, it appends a number in brackets, which is then incremented, in case these file names also are already taken. Please note, that this is a simplified version of the algorithm. In reality it is more complex.
Since I do not want to reverse engineer this behavior, is there a c# .Net-Api available that gives me direct access to this behavior when copying or creating files?
No.
Point mostly being that this is absolutely not a windows standard behavior, but only done in the Explorer (i.e. it is this particular program that does that).
As #TomTom said, no there isn't, but the Windows Explorer behaviour is easy to reproduce:
Given
var source = #"C:\Temp\Source.txt";
var targetFolder = #"C:\Temp\";
Then,
var targetName = Path.GetFileName(source);
var target = Path.Combine(targetFolder, targetName);
while (File.Exists(target)) {
targetName = "Copy of "+ targetName;
var target = Path.Combine(targetFolder, targetName);
}
File.Copy(source, target);
Or you can do a Mac like:
var targetName = Path.GetFileName(source);
for (int i = 0; i < MAX_TRIES; i++) {
var idx = (i==0)?"":(" ("+i.ToString()+")");
var target = Path.Combine(targetFolder, targetName+idx);
if (!File.Exists(target)) break;
}
File.Copy(source, target);

How do you Load an MP3 file without actually using the Content class?

I am a beginner at Programming with C Sharp, and I am using the XNA SDK. I am trying to make a simple game to help my fellow classmates with studying for school, and I decided I would like it if there was some way to have them put music they want to listen to while playing the game inside of a file, and have the game automatically Load the music files, and play them in a playlist.
So far, I am able to get the game to detect whether the files are music, by detecting whether the file path name Contains(".mp3") , but I am trying to actually load the file name into a list of type Song, using a for Loop. The code looks like this.
(Declaration)
List<Song> songsToPlay;
string[] fileNames
(Initialize)
fileNames[] = Directory.GetFiles(".\Music")
(LoadContent)
for (int i = 0; i < fileNames.Count(); i++)
{
if (fileNames[i].Contains(".mp3")
{
songsToPlay.Add(fileNames[i]);
}
}
I have been trying to find a way to add a whole folder to the Content Directory, and have it do something more like
for (int i = 0; i < fileNames.Count(); i++)
{
songsToPlay.Add(Content.Load<Song>("fileNames[i]")
}
I have been unable to find some way to do this... Does anyone know how to make this work, or a better way to do this?
If you have your files in your project content, you should use the ContentManager class. It gives you more than just file loading. For example you can use Content.Unload to unload all your data when you're no longer using it.
There is no need to avoid that class. This page has an example showing exactly what you're trying to do:
public static Dictionary<string, T> LoadContent<T>(
this ContentManager contentManager, string contentFolder)
{
var dir = new DirectoryInfo(contentManager.RootDirectory
+ "\\" + contentFolder);
if (!dir.Exists)
throw new DirectoryNotFoundException();
var result = new Dictionary<string, T>();
foreach (FileInfo file in dir.GetFiles("*.mp3"))
{
string key = Path.GetFileNameWithoutExtension(file.Name);
result[key] = contentManager.Load<T>(
contentManager.RootDirectory + "/" + contentFolder + "/" + key);
}
return result;
}
You can use it like this:
var songs = Content.LoadContent<Song>("Songs");
Slight improvement to this code...
Once you get the above code working, I also suggest you make a slight change:
var dir = new DirectoryInfo(
System.IO.Path.Combine(contentManager.RootDirectory, contentFolder));
You shouldn't manually build paths via string concatenation when you can possibly avoid it. I don't know that you can do the same for ContentManager paths tho, so you might have to stick with string concatenation for that case.
Edit: Too many constructs you haven't used in class yet
Since you haven't used extension methods or the static keyword in your class yet, and probably haven't used dictionaries, here's a simpler way to do this:
string contentFolder = "Music";
var dir = new DirectoryInfo(Content.RootDirectory + "\\" + contentFolder);
if (!dir.Exists)
{
// Todo: Display a message to the user instead?
throw new DirectoryNotFoundException();
}
string[] files = dir.GetFiles("*.mp3");
for (int i = 0; i < files.Count(); ++i)
{
FileInfo file = files[i];
string key = System.IO.Path.GetFileNameWithoutExtension(file.Name);
var song = Content.Load<Song>(
Content.RootDirectory + "/" + contentFolder + "/" + key);
songsToPlay.Add(song);
}
Edit2: Some explanation of this second code sample
The DirectoryInfo class lets you load up a directory so you can enumerate all the files in it.
The GetFiles method on DirectoryInfo lets you enumerate files using a wildcard style pattern matching. Wildcard pattern matching for files means that when given these patterns:
*.* - you are looking for files named <anything>.<anything>
*.mp3 - you are looking for <anything>.mp3
throw means throwing an exception. This will deliberately stop executing code and display a good error message ("directory not found") and a line number. There is a lot to learn about exception handling, so I won't try to do it justice with a description here.
GetFileNameWithoutExtension should be obvious because it is well named.
Content.RootDirectory + "/" + contentFolder + "/" + key
That last little bit of code will build up a string containing the content root directory, the sub-directory of your songs, and each file name, using a name it can understand (since it doesn't know about filename extensions).
var means "whatever type I assign to it". It is a short-cut. For example, instead of typing:
List<string> someList = new List<string>();
You type:
var someList = new List<string>();
var has to know what type is on the right-hand-side of the assignment. It is useful because you can avoid repeating yourself.
Using var doesn't bestow any magical abilities to the variable though. You won't be able to assign a variable of a different type once you've declared the variable. It is just a short-cut for the exact same functionality.
Use the Song.FromUri method:
Song.FromUri("audio name", new Uri(#"C:\audio.mp3"));
The filepath can't contain spaces!
See here for a workaround: XNA 4 Song.fromUri containing Spaces

Retrieve Windows Experience Rating

I'm looking to retrieve a machine's windows experience rating in C#. If possible I would also like to retrieve the numbers for each component (Graphics, RAM etc.)
Is this possible?
Every time the user goes through control panel to calculate the Windows Experience Rating, the system creates a new file in %Windows%\Performance\WinSAT\DataStore\
You need to find the most recent file (they are named with the most significant date first, so finding the latest file is trivial).
These files are xml files and are easy to parse with XmlReader or other xml parser.
The section you are interested in is WinSAT\WinSPR and contains all the scores in a single section. E.g.
<WinSAT>
<WinSPR>
<SystemScore>3.7</SystemScore>
<MemoryScore>5.9</MemoryScore>
<CpuScore>5.2</CpuScore>
<CPUSubAggScore>5.1</CPUSubAggScore>
<VideoEncodeScore>5.3</VideoEncodeScore>
<GraphicsScore>3.9</GraphicsScore>
<GamingScore>3.7</GamingScore>
<DiskScore>5.2</DiskScore>
</WinSPR>
...
Same with LINQ:
var dirName = Environment.ExpandEnvironmentVariables(#"%WinDir%\Performance\WinSAT\DataStore\");
var dirInfo = new DirectoryInfo(dirName);
var file = dirInfo.EnumerateFileSystemInfos("*Formal.Assessment*.xml")
.OrderByDescending(fi => fi.LastWriteTime)
.FirstOrDefault();
if (file == null)
throw new FileNotFoundException("WEI assessment xml not found");
var doc = XDocument.Load(file.FullName);
Console.WriteLine("Processor: " + doc.Descendants("CpuScore").First().Value);
Console.WriteLine("Memory (RAM): " + doc.Descendants("MemoryScore").First().Value);
Console.WriteLine("Graphics: " + doc.Descendants("GraphicsScore").First().Value);
Console.WriteLine("Gaming graphics: " + doc.Descendants("GamingScore").First().Value);
Console.WriteLine("Primary hard disk: " + doc.Descendants("DiskScore").First().Value);
Console.WriteLine("Base score: " + doc.Descendants("SystemScore").First().Value);
Here is a snippet for VB.NET. Converted to C# (using this, I haven't actually tested the code yet, though it looks to be fine).
/// <summary>
/// Gets the base score of a computer running Windows Vista or higher.
/// </summary>
/// <returns>The String Representation of Score, or False.</returns>
/// <remarks></remarks>
public string GetBaseScore()
{
// Check if the computer has a \WinSAT dir.
if (System.IO.Directory.Exists("C:\\Windows\\Performance\\WinSAT\\DataStore"))
{
// Our method to get the most recently updated score.
// Because the program makes a new XML file on every update of the score,
// we need to calculate the most recent, just incase the owner has upgraded.
System.IO.DirectoryInfo Dir = new System.IO.DirectoryInfo("C:\\Windows\\Performance\\WinSAT\\DataStore");
System.IO.FileInfo[] fileDir = null;
System.IO.FileInfo fileMostRecent = default(IO.FileInfo);
System.DateTime LastAccessTime = default(System.DateTime);
string LastAccessPath = string.Empty;
fileDir = Dir.GetFiles;
// Loop through the files in the \WinSAT dir to find the newest one.
foreach (var fileMostRecent in fileDir)
{
if (fileMostRecent.LastAccessTime >= LastAccessTime)
{
LastAccessTime = fileMostRecent.LastAccessTime;
LastAccessPath = fileMostRecent.FullName;
}
}
// Create an XmlTextReader instance.
System.Xml.XmlTextReader xmlReadScore = new System.Xml.XmlTextReader(LastAccessPath);
// Disable whitespace handling so we don't read over them
xmlReadScore.WhitespaceHandling = System.Xml.WhitespaceHandling.None;
// We need to get to the 25th tag, "WinSPR".
for (int i = 0; i <= 26; i++)
{
xmlReadScore.Read();
}
// Create a string variable, so we can clean up without any mishaps.
string SystemScore = xmlReadScore.ReadElementString("SystemScore");
// Clean up.
xmlReadScore.Close();
return SystemScore;
}
// Unsuccessful.
return false;
}
I guess it only returns the overall rating, but hopefully it should get you started at least. It may only be a matter of changing a filename/parameter to get the individual ratings.

Categories

Resources