$inputText = @"
reaction_other
[OBJECT:REACTION]
[REACTION:TAN_A_HIDE]
[NAME:tan a hide]
[BUILDING:TANNER:CUSTOM_T] ## Some comment here
[REAGENT:A:1:NONE:NONE:NONE:NONE] # Here [USE_BODY_COMPONENT] ## Inline comment [UNROTTEN]
[HAS_MATERIAL_REACTION_PRODUCT:TAN_MAT]
"@
$outputText = $inputText -replace '(?m)^\s*','' `
-replace '(\[.+?\][^\[\r\n]*)(?=\[)' , "`$1`r`n"
How This Works
It's still being done in 2 -replaces.
Replace 1:
The first -replace removes all of the leading whitespace:
(?m) is the inline regex mode specifier where m stands for "multiline" and it effectively makes ^ and $ match the beginning and end of each line rather than the beginning and end of the whole string.
^ is therefore matching the beginning of each line.
\s is a character class that stands for most whitespace, including spaces and tabs.
* means zero or more.
So effectively this says:
Replace all whitespace at the beginning of each line with an empty string (effectively removing it).
Replace 2:
We start by opening a capturing group with (.
Next we match a literal left bracket with \[, followed by 1 or more of any character (non-greedy), followed by a literal right bracket \].
Still in our capturing group, we start a character class by using [ (not escaped), and in this context ^ means NOT, so whatever we put inside the class must not be present.
Inside the character class, we use a literal left bracket \[, a CR \r and a LF \n, then close the class ]. We use * to indicate that we want 0 or more of that class.
The capturing group is now closed.
This next part is a positive lookahead. It looks to see if there is a particular match coming up, but the critical part here is that it does not consume those characters, therefore they are not part of the match, therefore they will not be replaced.
The replacement string is the value of the first capture group, followed by CRLF. Note the double quoted string. This requires us to escape the $ using backtick, so that powershell doesn't interpret $1 as a powershell variable. Instead a literal $1 string will be sent to the regex engine, where it will interpret as a backreference.
So this replace is basically saying:
Find a string inside of square brackets
- optionally followed by any amount of text that is not a [ or a line ending. But, only count it as a match if there is a [ directly following the matched string. If all conditions are met, replace that string with itself followed by CRLF.