C# program that dumps the entire HKLM registry tree to the console? - c#

I'm trying to write a simple console app that dumps the contents of HKLM to the console. The output should look something like:
HKEY_LOCAL_MACHINE
HKEY_LOCAL_MACHINE\BCD00000000
HKEY_LOCAL_MACHINE\BCD00000000\Description
KeyName: BCD00000000
System: 1
TreatAsSystem: 1
GuidCache: System.Byte[]
HKEY_LOCAL_MACHINE\BCD00000000\Objects
HKEY_LOCAL_MACHINE\BCD00000000\Objects\{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}
HKEY_LOCAL_MACHINE\BCD00000000\Objects\{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}\Description
Type: 537919488
HKEY_LOCAL_MACHINE\BCD00000000\Objects\{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}\Elements
HKEY_LOCAL_MACHINE\BCD00000000\Objects\{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}\Elements\16000020
Element: System.Byte[]
I haven't had much luck researching how to do this. Any help would be greatly appreciated.

You know there's already an app that dumps registry contents, right?
REG EXPORT HKLM hklm.reg
Fun part is, it exports the keys in a text format, but that text file can be imported using either REG or the registry editor.

cHao way is the safiest approach to your question. In the meanwhile, I was bored on this sunday night and wrote something. Just change the Console.WriteLine or add a few other Console.WriteLine to suit your need, whatever need there is.
class Program
{
static void Main(string[] args)
{
Registry.CurrentUser.GetSubKeyNames()
.Select(x => Registry.CurrentUser.OpenSubKey(x))
.Traverse(key =>
{
if (key != null)
{
// You will most likely hit some security exception
return key.GetSubKeyNames().Select(subKey => key.OpenSubKey(subKey));
}
return null;
})
.ForEach(key =>
{
key.GetValueNames()
.ForEach(valueName => Console.WriteLine("{0}\\{1}:{2} ({3})", key, valueName, key.GetValue(valueName), key.GetValueKind(valueName)));
});
Console.ReadLine();
}
}
public static class Extensions
{
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse)
{
foreach (T item in source)
{
yield return item;
IEnumerable<T> seqRecurse = fnRecurse(item);
if (seqRecurse != null)
{
foreach (T itemRecurse in Traverse(seqRecurse, fnRecurse))
{
yield return itemRecurse;
}
}
}
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
}

thanks for the answer Pierre-Alain Vigeant, i like ur solution. for the most part it worked with a couple of minor alterations for the text formatting, but i still couldnt deal with the security exception that was being thrown. turns out linq is not so great for this because it does alot of behind the scenes stuff. the following solution is a basic idea of how to do it
class Program
{
static void Main(string[] args)
{
RegistryKey key = Registry.LocalMachine;
Traverse(key, 0);
key.Close();
Console.Read();
}
private static void Traverse(RegistryKey key, int indent)
{
Console.WriteLine(key.Name);
string[] names = key.GetSubKeyNames();
foreach (var subkeyname in names)
{
try
{
string[] valnames = key.GetValueNames();
foreach (string valname in valnames)
{
Console.WriteLine(returnIndentions(indent)+valname + ":" + key.GetValue(valname));
}
Traverse(key.OpenSubKey(subkeyname),indent++);
}
catch {
//do nothing
}
}
}
private static string returnIndentions(int indent)
{
string indentions = "";
for (int i = 0; i < indent; i++) {
indentions += " ";
}
return indentions;
}
}

using System;
using System.Text;
using Microsoft.Win32;
class Program
{
static void Main(string[] args)
{
using RegistryKey key = Registry.LocalMachine;
string keyName = args[0]; // eg #"SOFTWARE\Microsoft\Speech\Voices"
var sb = new StringBuilder();
var subKey = key.OpenSubKey(keyName);
Traverse(subKey);
void Traverse(RegistryKey key, int indent = 0)
{
sb.AppendLine(new string(' ', Math.Max(0, indent - 2)) + key.Name);
indent++;
string[] valnames = key.GetValueNames();
foreach (string valname in valnames)
{
sb.AppendLine(new string(' ', indent) + valname + " : " + key.GetValue(valname));
}
string[] names = key.GetSubKeyNames();
foreach (var subkeyname in names)
{
Traverse(key.OpenSubKey(subkeyname), indent + 2);
}
}
Console.WriteLine(sb.ToString());
}
}

Related

How do I parse and find a string in a C# script?

I've been trying to parse and search for a specific word in a big string, but I can't seem to be able to figure it out. I have created a script that connects a Twitch Channel's chat into unity.
An example of a message would be:
"#badge-info=subscriber/4;badges=moderator/1,subscriber/3,bits/1;bits=1;color=;display-name=TwitchUser1234;emotes=;flags=;id=da6ec4c6-af61-4346-abc-123456789;mod=1;room-id=12345678;subscriber=1;tmi-sent-ts=160987654321;turbo=0;user-id=123456789;user-type=mod :TwitchUser1234#TwitchUser1234.tmi.twitch.tv PRIVMSG #thechannelyouarewatching :PogChamp1 Another Test Bit"
I tried parsing and searching for the string 'bits' the message by doing:
private void GameInputs(string ChatInputs)
{
string Search;
Search = ChatInputs.Split(";", "=");
if(string "bits" in Search)
{
print("I made it here.");
}
}
I'm at a complete loss and have no idea how to do this. Any help is appreciated.
If my full code is needed it is:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.ComponentModel;
using System.Net.Sockets;
using System.IO;
public class TwitchChat : MonoBehaviour
{
private TcpClient twitchClient;
private StreamReader reader;
private StreamWriter writer;
public string username, password, channelName; // http://twitchapps.com/tmi
// Start is called before the first frame update
void Start()
{
Connect();
}
void Update()
{
if(!twitchClient.Connected)
{
Connect();
}
ReadChat();
}
private void Connect()
{
twitchClient = new TcpClient("irc.chat.twitch.tv", 6667);
reader = new StreamReader(twitchClient.GetStream());
writer = new StreamWriter(twitchClient.GetStream());
writer.WriteLine("PASS " + password);
writer.WriteLine("NICK " + username);
writer.WriteLine("USER " + username + " 8 * :" + username);
writer.WriteLine("JOIN #" + channelName);
writer.WriteLine("CAP REQ :twitch.tv/tags");
writer.Flush();
}
private void ReadChat()
{
if (twitchClient.Available > 0)
{
var message = reader.ReadLine();
print(message);
GameInputs(message);
}
}
private void GameInputs(string ChatInputs)
{
string Search;
Search = ChatInputs.Split(";", "=");
if(string "bits" in Search)
{
print("I made it here.");
}
}
}
If you want to pull the value of "bits=xx" out, this would do it:
var b = value.Split(';').FirstOrDefault(s => s.StartsWith("bits="))?[5..];
b will be null if "bits=" is not present
If you're going to parse a lot of values out of this string consider turning it into a dictionary:
var c = new []{'='};
var d = value.Split(';').ToDictionary(s => s.Split(c,2)[0], s => s.Split(c,2)[1]);
It's slightly inefficient to split twice, if it bothers you, you can sub string:
value.Split(';').ToDictionary(s => s[..s.IndexOf('=')], s => s[s.IndexOf('=')+1..]);
This gives a dictionary of string, so you can do like:
if(d.ContainsKey("bits")){
var bits = int.Parse(d["bits"]);
...
String has a method Contains(string) that does the job:
if (ChantInputs.Contains("bits")
{
print("I made it here.");
}
You can try below.
private void GameInputs(string ChatInputs)
{
string[] Search = ChatInputs.Split(new char[] { ';', '=' });
foreach(string s in Search)
{
if(s == "bits")
{
print("I made it here.");
}
}
}
Below is the working code.
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
string str = "one;two;test;three;test+test";
string[] strs = str.Split(new char[] { ';', '+' });
foreach(string s in strs)
{
if(s == "test")
{
Console.WriteLine(s);
}
}
Console.ReadLine();
}
}
}

What is wrong here in my C# Program?

I want to search a particular word in a defined string for which I am using the foreach key word, but it's not working.
I am just a beginner at this. Please help me what is wrong in this and I don't want to use arrays.
static void Main(string[] args)
{
string str = "Hello You are welcome";
foreach (string item in str) // can we use string here?
{
if (str.Contains(are); // I am checking if the word "are" is present in the above string
Console.WriteLine("True");
)
}
string str = "Hello You are welcome";
if (str.Contains("are"))
{
Console.WriteLine("True");
}
or you mean:
string str = "Hello You are welcome";
foreach (var word in str.Split()) // split the string (by space)
{
if (word == "are")
{
Console.WriteLine("True");
}
}
Try this
static void Main(string[] args)
{
string str = "Hello You are welcome";
foreach (var item in str.Split(' ')) // split the string (by space)
{
if (item == "are")
{
Console.WriteLine("True");
}
}
}

C# - Assign ConsoleKey then reassign using ConsoleKeyInfo

What I'm trying to accomplish is have a ConsoleKey assigned to a variable then using ConsoleKeyInfo to modify the variable.
I'm getting errors saying Cannot convert source type 'System.ConsoleKey' to target type 'System.ConsoleKeyInfo'.
The reasoning behind this is I wish to have the user be able to reprogram the keys used inside the application.
I have this so far;
public static ConsoleKeyInfo keyboardkeynorth;
keyboardkeynorth = Console.ReadKey();
This works, but it doesn't allow me to start the program with keyboardkeynorth already set to ConsoleKey.W.
Elsewhere in the program I would call keyboardkeynorth to be used as a ConsoleKey
This may be simple but it seems to be eluding me.
Realizing this is a short 3+ years after you asked...
public static ConsoleKeyInfo keyboardkeynorth =
new ConsoleKeyInfo('W', ConsoleKey.W, false, false, false);
And don't you want "north" to be 'N' initially? ;^)
It's a strange, verbose constructor, but does what you appear to be asking.
Here is a complete, and simple, program to show you how you might do this. I say simple because it doesn't take into consideration CTRL, ALT, or SHIFT -- and based on how I gather the custom key info you wouldn't even be able to set those because I'm using ReadKey. But you'll get the idea.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
namespace ConsoleApplication23
{
class Program
{
private static ConsoleKeyInfo keyAction1, keyAction2, keyAction3;
static void Main(string[] args)
{
InitializeKeyActions();
Console.Write("Do you want a new key action for #1? ");
var result = Console.ReadKey();
if (result.Key != ConsoleKey.Enter) { UpdateKeyInfo("keyAction1", result); }
}
private static void UpdateKeyInfo(string keyName, ConsoleKeyInfo result)
{
var propertyInfo = typeof(Program).GetField(keyName, BindingFlags.NonPublic | BindingFlags.Static);
if (propertyInfo == null) { return; }
var key = result.KeyChar;
propertyInfo.SetValue(null, new ConsoleKeyInfo(key, (ConsoleKey)key, false, false, false));
StringBuilder keyActions = new StringBuilder();
foreach (var line in File.ReadAllLines("keyactions.ini"))
{
var kvp = line.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (kvp[0] != keyName)
{
keyActions.AppendLine(line);
continue;
}
keyActions.AppendLine(string.Format("{0}: {1}", kvp[0], result.KeyChar));
}
File.WriteAllText("keyactions.ini", keyActions.ToString());
}
private static void InitializeKeyActions()
{
if (!File.Exists("keyactions.ini"))
{
StringBuilder keyActions = new StringBuilder();
keyActions.AppendLine("keyAction1: A");
keyActions.AppendLine("keyAction2: B");
keyActions.AppendLine("keyAction3: C");
File.WriteAllText("keyactions.ini", keyActions.ToString());
}
foreach (var line in File.ReadAllLines("keyactions.ini"))
{
var kvp = line.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
var propertyInfo = typeof(Program).GetField(kvp[0], BindingFlags.NonPublic | BindingFlags.Static);
if (propertyInfo == null) { continue; }
var key = kvp[1].Trim()[0];
propertyInfo.SetValue(null, new ConsoleKeyInfo(key, (ConsoleKey)key, false, false, false));
}
}
}
}

Copying subcategories in registry

can someone suggest the best way how to copy content of one subcategory in registry to another?
For example we have:
HKEY_CURRENT_USER.Software.MyProgram.ver_1
and running the function check, if there no ver_2, so create ...MyProgram.ver_2 and copy there all content of ...MyProgram.ver_1.
An extension method:
public static void CopyTo(this RegistryKey src, RegistryKey dst)
{
// copy the values
foreach (var name in src.GetValueNames())
{
dst.SetValue(name, src.GetValue(name), src.GetValueKind(name));
}
// copy the subkeys
foreach (var name in src.GetSubKeyNames())
{
using (var srcSubKey = src.OpenSubKey(name, false))
{
var dstSubKey = dst.CreateSubKey(name);
srcSubKey.CopyTo(dstSubKey);
}
}
}
Called like this:
var ver1 = Registry.CurrentUser.OpenSubKey(#"Software\MyProgram\ver_1");
var ver2 = Registry.CurrentUser.OpenSubKey(#"Software\MyProgram\ver_2");
ver1.CopyTo(ver2);
Based on Wallys extension method I implemented an MoveTo extension method.
This code also contains an key.GetParent(...) extension method which will open the parent key (needed because I use DeleteSubKeyTree method to delete the Key after copy succeded.
public static void MoveTo(this RegistryKey src, RegistryKey dst)
{
src.CopyTo(dst);
src.Delete();
}
public static void Delete(this RegistryKey key)
{
using (var parentKey = key.GetParent(true))
{
var keyName = key.Name.Split('\\').Last();
parentKey.DeleteSubKeyTree(keyName);
}
}
public static RegistryKey GetParent(this RegistryKey key)
{
return key.GetParent(false);
}
public static RegistryKey GetParent(this RegistryKey key, bool writable)
{
var items = key.Name.Split('\\');
var hiveName = items.First();
var parentKeyName = String.Join("\\", items.Skip(1).Reverse().Skip(1).Reverse());
var hive = GetHive(hiveName);
using (var baseKey = RegistryKey.OpenBaseKey(hive, key.View))
{
return baseKey.OpenSubKey(parentKeyName, writable);
}
}
private static RegistryHive GetHive(string hiveName)
{
if (hiveName.Equals("HKEY_CLASSES_ROOT", StringComparison.OrdinalIgnoreCase))
return RegistryHive.ClassesRoot;
else if (hiveName.Equals("HKEY_CURRENT_USER", StringComparison.OrdinalIgnoreCase))
return RegistryHive.CurrentUser;
else if (hiveName.Equals("HKEY_LOCAL_MACHINE", StringComparison.OrdinalIgnoreCase))
return RegistryHive.LocalMachine;
else if (hiveName.Equals("HKEY_USERS", StringComparison.OrdinalIgnoreCase))
return RegistryHive.Users;
else if (hiveName.Equals("HKEY_PERFORMANCE_DATA", StringComparison.OrdinalIgnoreCase))
return RegistryHive.PerformanceData;
else if (hiveName.Equals("HKEY_CURRENT_CONFIG", StringComparison.OrdinalIgnoreCase))
return RegistryHive.CurrentConfig;
else if (hiveName.Equals("HKEY_DYN_DATA", StringComparison.OrdinalIgnoreCase))
return RegistryHive.DynData;
else
throw new NotImplementedException(hiveName);
}
This question answer the "how to read/write registry" question, and based on that you can do this:
read "ver_2" key
if "ver_2" doesn't exist, create it
if "ver_2" didn't exist above, read each value in "ver_1" key and write an identical value under "ver_2"
Basically, you read a value, write it, then read another, write it, and so on.

C# Sanitize File Name

I recently have been moving a bunch of MP3s from various locations into a repository. I had been constructing the new file names using the ID3 tags (thanks, TagLib-Sharp!), and I noticed that I was getting a System.NotSupportedException:
"The given path's format is not supported."
This was generated by either File.Copy() or Directory.CreateDirectory().
It didn't take long to realize that my file names needed to be sanitized. So I did the obvious thing:
public static string SanitizePath_(string path, char replaceChar)
{
string dir = Path.GetDirectoryName(path);
foreach (char c in Path.GetInvalidPathChars())
dir = dir.Replace(c, replaceChar);
string name = Path.GetFileName(path);
foreach (char c in Path.GetInvalidFileNameChars())
name = name.Replace(c, replaceChar);
return dir + name;
}
To my surprise, I continued to get exceptions. It turned out that ':' is not in the set of Path.GetInvalidPathChars(), because it is valid in a path root. I suppose that makes sense - but this has to be a pretty common problem. Does anyone have some short code that sanitizes a path? The most thorough I've come up with this, but it feels like it is probably overkill.
// replaces invalid characters with replaceChar
public static string SanitizePath(string path, char replaceChar)
{
// construct a list of characters that can't show up in filenames.
// need to do this because ":" is not in InvalidPathChars
if (_BadChars == null)
{
_BadChars = new List<char>(Path.GetInvalidFileNameChars());
_BadChars.AddRange(Path.GetInvalidPathChars());
_BadChars = Utility.GetUnique<char>(_BadChars);
}
// remove root
string root = Path.GetPathRoot(path);
path = path.Remove(0, root.Length);
// split on the directory separator character. Need to do this
// because the separator is not valid in a filename.
List<string> parts = new List<string>(path.Split(new char[]{Path.DirectorySeparatorChar}));
// check each part to make sure it is valid.
for (int i = 0; i < parts.Count; i++)
{
string part = parts[i];
foreach (char c in _BadChars)
{
part = part.Replace(c, replaceChar);
}
parts[i] = part;
}
return root + Utility.Join(parts, Path.DirectorySeparatorChar.ToString());
}
Any improvements to make this function faster and less baroque would be much appreciated.
To clean up a file name you could do this
private static string MakeValidFileName( string name )
{
string invalidChars = System.Text.RegularExpressions.Regex.Escape( new string( System.IO.Path.GetInvalidFileNameChars() ) );
string invalidRegStr = string.Format( #"([{0}]*\.+$)|([{0}]+)", invalidChars );
return System.Text.RegularExpressions.Regex.Replace( name, invalidRegStr, "_" );
}
A shorter solution:
var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');
Based on Andre's excellent answer but taking into account Spud's comment on reserved words, I made this version:
/// <summary>
/// Strip illegal chars and reserved words from a candidate filename (should not include the directory path)
/// </summary>
/// <remarks>
/// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name
/// </remarks>
public static string CoerceValidFileName(string filename)
{
var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
var invalidReStr = string.Format(#"[{0}]+", invalidChars);
var reservedWords = new []
{
"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4",
"COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
};
var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_");
foreach (var reservedWord in reservedWords)
{
var reservedWordPattern = string.Format("^{0}\\.", reservedWord);
sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase);
}
return sanitisedNamePart;
}
And these are my unit tests
[Test]
public void CoerceValidFileName_SimpleValid()
{
var filename = #"thisIsValid.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual(filename, result);
}
[Test]
public void CoerceValidFileName_SimpleInvalid()
{
var filename = #"thisIsNotValid\3\\_3.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("thisIsNotValid_3__3.txt", result);
}
[Test]
public void CoerceValidFileName_InvalidExtension()
{
var filename = #"thisIsNotValid.t\xt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("thisIsNotValid.t_xt", result);
}
[Test]
public void CoerceValidFileName_KeywordInvalid()
{
var filename = "aUx.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("_reservedWord_.txt", result);
}
[Test]
public void CoerceValidFileName_KeywordValid()
{
var filename = "auxillary.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("auxillary.txt", result);
}
string clean = String.Concat(dirty.Split(Path.GetInvalidFileNameChars()));
there are a lot of working solutions here. just for the sake of completeness, here's an approach that doesn't use regex, but uses LINQ:
var invalids = Path.GetInvalidFileNameChars();
filename = invalids.Aggregate(filename, (current, c) => current.Replace(c, '_'));
Also, it's a very short solution ;)
I'm using the System.IO.Path.GetInvalidFileNameChars() method to check invalid characters and I've got no problems.
I'm using the following code:
foreach( char invalidchar in System.IO.Path.GetInvalidFileNameChars())
{
filename = filename.Replace(invalidchar, '_');
}
I wanted to retain the characters in some way, not just simply replace the character with an underscore.
One way I thought was to replace the characters with similar looking characters which are (in my situation), unlikely to be used as regular characters. So I took the list of invalid characters and found look-a-likes.
The following are functions to encode and decode with the look-a-likes.
This code does not include a complete listing for all System.IO.Path.GetInvalidFileNameChars() characters. So it is up to you to extend or utilize the underscore replacement for any remaining characters.
private static Dictionary<string, string> EncodeMapping()
{
//-- Following characters are invalid for windows file and folder names.
//-- \/:*?"<>|
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add(#"\", "Ì"); // U+OOCC
dic.Add("/", "Í"); // U+OOCD
dic.Add(":", "¦"); // U+00A6
dic.Add("*", "¤"); // U+00A4
dic.Add("?", "¿"); // U+00BF
dic.Add(#"""", "ˮ"); // U+02EE
dic.Add("<", "«"); // U+00AB
dic.Add(">", "»"); // U+00BB
dic.Add("|", "│"); // U+2502
return dic;
}
public static string Escape(string name)
{
foreach (KeyValuePair<string, string> replace in EncodeMapping())
{
name = name.Replace(replace.Key, replace.Value);
}
//-- handle dot at the end
if (name.EndsWith(".")) name = name.CropRight(1) + "°";
return name;
}
public static string UnEscape(string name)
{
foreach (KeyValuePair<string, string> replace in EncodeMapping())
{
name = name.Replace(replace.Value, replace.Key);
}
//-- handle dot at the end
if (name.EndsWith("°")) name = name.CropRight(1) + ".";
return name;
}
You can select your own look-a-likes. I used the Character Map app in windows to select mine %windir%\system32\charmap.exe
As I make adjustments through discovery, I will update this code.
I think the problem is that you first call Path.GetDirectoryName on the bad string. If this has non-filename characters in it, .Net can't tell which parts of the string are directories and throws. You have to do string comparisons.
Assuming it's only the filename that is bad, not the entire path, try this:
public static string SanitizePath(string path, char replaceChar)
{
int filenamePos = path.LastIndexOf(Path.DirectorySeparatorChar) + 1;
var sb = new System.Text.StringBuilder();
sb.Append(path.Substring(0, filenamePos));
for (int i = filenamePos; i < path.Length; i++)
{
char filenameChar = path[i];
foreach (char c in Path.GetInvalidFileNameChars())
if (filenameChar.Equals(c))
{
filenameChar = replaceChar;
break;
}
sb.Append(filenameChar);
}
return sb.ToString();
}
I have had success with this in the past.
Nice, short and static :-)
public static string returnSafeString(string s)
{
foreach (char character in Path.GetInvalidFileNameChars())
{
s = s.Replace(character.ToString(),string.Empty);
}
foreach (char character in Path.GetInvalidPathChars())
{
s = s.Replace(character.ToString(), string.Empty);
}
return (s);
}
Here's an efficient lazy loading extension method based on Andre's code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LT
{
public static class Utility
{
static string invalidRegStr;
public static string MakeValidFileName(this string name)
{
if (invalidRegStr == null)
{
var invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
invalidRegStr = string.Format(#"([{0}]*\.+$)|([{0}]+)", invalidChars);
}
return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, "_");
}
}
}
Your code would be cleaner if you appended the directory and filename together and sanitized that rather than sanitizing them independently. As for sanitizing away the :, just take the 2nd character in the string. If it is equal to "replacechar", replace it with a colon. Since this app is for your own use, such a solution should be perfectly sufficient.
using System;
using System.IO;
using System.Linq;
using System.Text;
public class Program
{
public static void Main()
{
try
{
var badString = "ABC\\DEF/GHI<JKL>MNO:PQR\"STU\tVWX|YZA*BCD?EFG";
Console.WriteLine(badString);
Console.WriteLine(SanitizeFileName(badString, '.'));
Console.WriteLine(SanitizeFileName(badString));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static string SanitizeFileName(string fileName, char? replacement = null)
{
if (fileName == null) { return null; }
if (fileName.Length == 0) { return ""; }
var sb = new StringBuilder();
var badChars = Path.GetInvalidFileNameChars().ToList();
foreach (var #char in fileName)
{
if (badChars.Contains(#char))
{
if (replacement.HasValue)
{
sb.Append(replacement.Value);
}
continue;
}
sb.Append(#char);
}
return sb.ToString();
}
}
Based #fiat's and #Andre's approach, I'd like to share my solution too.
Main difference:
its an extension method
regex is compiled at first use to save some time with a lot executions
reserved words are preserved
public static class StringPathExtensions
{
private static Regex _invalidPathPartsRegex;
static StringPathExtensions()
{
var invalidReg = System.Text.RegularExpressions.Regex.Escape(new string(Path.GetInvalidFileNameChars()));
_invalidPathPartsRegex = new Regex($"(?<reserved>^(CON|PRN|AUX|CLOCK\\$|NUL|COM0|COM1|COM2|COM3|COM4|COM5|COM6|COM7|COM8|COM9|LPT0|LPT1|LPT2|LPT3|LPT4|LPT5|LPT6|LPT7|LPT8|LPT9))|(?<invalid>[{invalidReg}:]+|\\.$)", RegexOptions.Compiled);
}
public static string SanitizeFileName(this string path)
{
return _invalidPathPartsRegex.Replace(path, m =>
{
if (!string.IsNullOrWhiteSpace(m.Groups["reserved"].Value))
return string.Concat("_", m.Groups["reserved"].Value);
return "_";
});
}
}

Categories

Resources