Skip to content

Instantly share code, notes, and snippets.

@aetos382
Last active June 26, 2026 07:18
Show Gist options
  • Select an option

  • Save aetos382/9afcb1eebb241ec62905ed60d9ef9130 to your computer and use it in GitHub Desktop.

Select an option

Save aetos382/9afcb1eebb241ec62905ed60d9ef9130 to your computer and use it in GitHub Desktop.
MSBuild の文字列エスケープはわけわからん
<Project DefaultTargets="Show">
<PropertyGroup>
<!-- リテラルとして ; を書くとエスケープされない -->
<NormalUnescapedProperty>a;b</NormalUnescapedProperty>
<!-- プロパティ関数の結果として返された文字列はエスケープされる -->
<AutomaticallyEscapedFunctionResultProperty>$([System.String]::Concat('a', ';', 'b'))</AutomaticallyEscapedFunctionResultProperty>
<!-- 明示的に %NN という形式で書いた文字列もエスケープされた判定 -->
<ExplicitlyEscapedProperty>a%3Bb</ExplicitlyEscapedProperty>
<!-- Escape 関数でエスケープしてみる -->
<ExplicitlyEscapedByFunctionProperty>$([MSBuild]::Escape($(NormalUnescapedProperty)))</ExplicitlyEscapedByFunctionProperty>
<ExplicitlyEscapedByFunctionPropertyContainsPercentSign>false</ExplicitlyEscapedByFunctionPropertyContainsPercentSign>
<ExplicitlyEscapedByFunctionPropertyContainsPercentSign Condition="$(ExplicitlyEscapedByFunctionProperty.Contains('%'))">true</ExplicitlyEscapedByFunctionPropertyContainsPercentSign>
<ExplicitlyEscapedByFunctionPropertyContainsCharacterCode>false</ExplicitlyEscapedByFunctionPropertyContainsCharacterCode>
<ExplicitlyEscapedByFunctionPropertyContainsCharacterCode Condition="$(ExplicitlyEscapedByFunctionProperty.Contains('3B'))">true</ExplicitlyEscapedByFunctionPropertyContainsCharacterCode>
<!-- 見た目としてはどれも同じ。比較しても同じ。 -->
<NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty>false</NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty>
<NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty Condition="'$(NormalUnescapedProperty)' == '$(AutomaticallyEscapedFunctionResultProperty)'">true</NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty>
<NormalUnescapedPropertyEqualsExplicitlyEscapedProperty>false</NormalUnescapedPropertyEqualsExplicitlyEscapedProperty>
<NormalUnescapedPropertyEqualsExplicitlyEscapedProperty Condition="'$(NormalUnescapedProperty)' == '$(ExplicitlyEscapedProperty)'">true</NormalUnescapedPropertyEqualsExplicitlyEscapedProperty>
<ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty>false</ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty>
<ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty Condition="'$(ExplicitlyEscapedByFunctionProperty)' == '$(NormalUnescapedProperty)'">true</ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty>
<!-- 値自体はエスケープ形式ではない(% や 3B は値として含んでいない) -->
<ExplicitlyEscapedPropertyContainsPercentSign>false</ExplicitlyEscapedPropertyContainsPercentSign>
<ExplicitlyEscapedPropertyContainsPercentSign Condition="$(ExplicitlyEscapedProperty.Contains('%'))">true</ExplicitlyEscapedPropertyContainsPercentSign>
<ExplicitlyEscapedPropertyContainsCharacterCode>false</ExplicitlyEscapedPropertyContainsCharacterCode>
<ExplicitlyEscapedPropertyContainsCharacterCode Condition="$(ExplicitlyEscapedProperty.Contains('3B'))">true</ExplicitlyEscapedPropertyContainsCharacterCode>
<!-- 明示的にアンエスケープ -->
<UnescapedAutomaticallyEscapedFunctionResultProperty>$([MSBuild]::Unescape($(AutomaticallyEscapedFunctionResultProperty)))</UnescapedAutomaticallyEscapedFunctionResultProperty>
<UnescapedExplicitlyEscapedProperty>$([MSBuild]::Unescape($(ExplicitlyEscapedProperty)))</UnescapedExplicitlyEscapedProperty>
<!-- 文字列連結によって組み立てる -->
<CharA>a</CharA>
<CharB>b</CharB>
<CharSemicolon>;</CharSemicolon>
<CharPercent>%</CharPercent>
<CharExclamation>!</CharExclamation>
<Char3B>3B</Char3B>
<Concatenated1>$(CharA)$(CharSemicolon)$(CharB)</Concatenated1>
<Concatenated2>$(CharA)$(CharPercent)$(Char3B)$(CharB)</Concatenated2>
<Concatenated1EqualsConcatenated2>false</Concatenated1EqualsConcatenated2>
<Concatenated1EqualsConcatenated2 Condition="'$(Concatenated1)' == '$(Concatenated2)'">true</Concatenated1EqualsConcatenated2>
<Concatenated2ContainsPercentSign>false</Concatenated2ContainsPercentSign>
<Concatenated2ContainsPercentSign Condition="$(Concatenated2.Contains('%'))">true</Concatenated2ContainsPercentSign>
<Concatenated2ContainsCharacterCode>false</Concatenated2ContainsCharacterCode>
<Concatenated2ContainsCharacterCode Condition="$(Concatenated2.Contains('3B'))">true</Concatenated2ContainsCharacterCode>
<UnescapedConcatenated2>$([MSBuild]::Unescape($(Concatenated2)))</UnescapedConcatenated2>
<Concatenated3>$(CharA)$(CharExclamation)$(Char3B)$(CharB)</Concatenated3>
<ReplacedConcatenated3>$(Concatenated3.Replace('!', '%'))</ReplacedConcatenated3>
<ReplacedConcatenated3EqualsConcatenated1>false</ReplacedConcatenated3EqualsConcatenated1>
<ReplacedConcatenated3EqualsConcatenated1 Condition="'$(ReplacedConcatenated3)' == '$(Concatenated1)'">true</ReplacedConcatenated3EqualsConcatenated1>
<UnescapedReplacedConcatenated3>$([MSBuild]::Unescape($(ReplacedConcatenated3)))</UnescapedReplacedConcatenated3>
<UnescapedReplacedConcatenated3EqualsConcatenated1>false</UnescapedReplacedConcatenated3EqualsConcatenated1>
<UnescapedReplacedConcatenated3EqualsConcatenated1 Condition="'$(UnescapedReplacedConcatenated3)' == '$(Concatenated1)'">true</UnescapedReplacedConcatenated3EqualsConcatenated1>
<!-- %25 は % のエスケープ シーケンス -->
<MultipleEscapedProperty>a%253Bb</MultipleEscapedProperty>
<UnescapedMultipleEscapedProperty>$([MSBuild]::Unescape($(MultipleEscapedProperty)))</UnescapedMultipleEscapedProperty>
<MultipleEscapedPropertyEqualsReplacedConcatenated3>false</MultipleEscapedPropertyEqualsReplacedConcatenated3>
<MultipleEscapedPropertyEqualsReplacedConcatenated3 Condition="'$(MultipleEscapedProperty)' == '$(ReplacedConcatenated3)'">true</MultipleEscapedPropertyEqualsReplacedConcatenated3>
<!-- エスケープの段数が 1 つずれているので等しくない -->
<MultipleEscapedEqualsExplicitlyEscaped>false</MultipleEscapedEqualsExplicitlyEscaped>
<MultipleEscapedEqualsExplicitlyEscaped Condition="'$(MultipleEscapedProperty)' == '$(ExplicitlyEscapedProperty)'">true</MultipleEscapedEqualsExplicitlyEscaped>
</PropertyGroup>
<ItemGroup>
<NormalUnescapedPropertyAsItem Include="$(NormalUnescapedProperty)" />
<AutomaticallyEscapedFunctionResultPropertyAsItem Include="$(AutomaticallyEscapedFunctionResultProperty)" />
<ExplicitlyEscapedPropertyAsItem Include="$(ExplicitlyEscapedProperty)" />
<ExplicitlyEscapedByFunctionPropertyAsItem Include="$(ExplicitlyEscapedByFunctionProperty)" />
<UnescapedAutomaticallyEscapedFunctionResultPropertyAsItem Include="$(UnescapedAutomaticallyEscapedFunctionResultProperty)" />
<UnescapedExplicitlyEscapedPropertyAsItem Include="$(UnescapedExplicitlyEscapedProperty)" />
<Concatenated1AsItem Include="$(Concatenated1)" />
<Concatenated2AsItem Include="$(Concatenated2)" />
<UnescapedConcatenated2AsItem Include="$(UnescapedConcatenated2)" />
<ReplacedConcatenated3AsItem Include="$(ReplacedConcatenated3)" />
<UnescapedReplacedConcatenated3AsItem Include="$(UnescapedReplacedConcatenated3)" />
<MultipleEscapedPropertyAsItem Include="$(MultipleEscapedProperty)" />
<UnescapedMultipleEscapedPropertyAsItem Include="$(UnescapedMultipleEscapedProperty)" />
</ItemGroup>
<Target Name="Show">
<Message Text="NormalUnescapedProperty: $(NormalUnescapedProperty)" />
<Message Text="AutomaticallyEscapedFunctionResultProperty: $(AutomaticallyEscapedFunctionResultProperty)" />
<Message Text="ExplicitlyEscapedProperty: $(ExplicitlyEscapedProperty)" />
<!-- 見た目としてはどれも同じ。比較しても同じ。 -->
<Message Text="NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty: $(NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty)" />
<Message Text="NormalUnescapedPropertyEqualsExplicitlyEscapedProperty: $(NormalUnescapedPropertyEqualsExplicitlyEscapedProperty)" />
<!-- 値自体はエスケープ形式ではない -->
<Message Text="ExplicitlyEscapedPropertyContainsPercentSign: $(ExplicitlyEscapedPropertyContainsPercentSign)" />
<Message Text="ExplicitlyEscapedPropertyContainsCharacterCode: $(ExplicitlyEscapedPropertyContainsCharacterCode)" />
<!-- Escape(a;b) しても見た目は a;b -->
<Message Text="ExplicitlyEscapedByFunctionProperty: $(ExplicitlyEscapedByFunctionProperty)" />
<Message Text="ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty: $(ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty)" />
<!-- Escape 関数を通しても値は % や 3B を含まない -->
<Message Text="ExplicitlyEscapedByFunctionPropertyContainsPercentSign: $(ExplicitlyEscapedByFunctionPropertyContainsPercentSign)" />
<Message Text="ExplicitlyEscapedByFunctionPropertyContainsCharacterCode: $(ExplicitlyEscapedByFunctionPropertyContainsCharacterCode)" />
<!-- 生の ; はアイテム区切りになる(Count = 2) -->
<Message Text="NormalUnescapedPropertyAsItem.Count: @(NormalUnescapedPropertyAsItem->Count())" />
<!-- エスケープされた ; はアイテム区切りにならない(Count = 1) -->
<Message Text="AutomaticallyEscapedFunctionResultPropertyAsItem.Count: @(AutomaticallyEscapedFunctionResultPropertyAsItem->Count())" />
<Message Text="ExplicitlyEscapedPropertyAsItem.Count: @(ExplicitlyEscapedPropertyAsItem->Count())" />
<!-- Unescape すればアイテム区切りとして使われる(Count = 2) -->
<Message Text="UnescapedAutomaticallyEscapedFunctionResultProperty.Count: @(UnescapedAutomaticallyEscapedFunctionResultPropertyAsItem->Count())" />
<Message Text="UnescapedExplicitlyEscapedProperty.Count: @(UnescapedExplicitlyEscapedPropertyAsItem->Count())" />
<!-- Escape 関数でエスケープしたものはアイテム区切りにならない(Count = 1) -->
<Message Text="ExplicitlyEscapedByFunctionPropertyAsItem.Count: @(ExplicitlyEscapedByFunctionPropertyAsItem->Count())" />
<!-- a と ; と b を文字列連結で組み立てたもの -->
<Message Text="Concatenated1: $(Concatenated1)" />
<!-- a と % と 3b と b を文字列連結で組み立てたもの -->
<Message Text="Concatenated2: $(Concatenated2)" />
<!-- % や 3b は含んでいない -->
<Message Text="Concatenated2ContainsPercentSign: $(Concatenated2ContainsPercentSign)" />
<Message Text="Concatenated2ContainsCharacterCode: $(Concatenated2ContainsCharacterCode)" />
<!-- 値としては同じ -->
<Message Text="Concatenated1EqualsConcatenated2: $(Concatenated1EqualsConcatenated2)" />
<!-- アイテム区切りとして使われる(Count = 2) -->
<Message Text="Concatenated1AsItem.Count: @(Concatenated1AsItem->Count())" />
<!-- アイテム区切りとして使われない(Count = 1) -->
<Message Text="Concatenated2AsItem.Count: @(Concatenated2AsItem->Count())" />
<!-- Unescape すればアイテム区切りとして使われる(Count = 2) -->
<Message Text="UnescapedConcatenated2AsItem.Count: @(UnescapedConcatenated2AsItem->Count())" />
<Message Text="Concatenated3: $(Concatenated3)" />
<!-- この値は 'a%3Bb' -->
<Message Text="ReplacedConcatenated3: $(ReplacedConcatenated3)" />
<!-- 'a%3Bb' != 'a;b' -->
<Message Text="ReplacedConcatenated3EqualsConcatenated1: $(ReplacedConcatenated3EqualsConcatenated1)" />
<!-- '%3B' は当然アイテム区切りでもない(Count = 1)-->
<Message Text="ReplacedConcatenated3AsItem.Count: @(ReplacedConcatenated3AsItem->Count())" />
<!-- Unescape すれば値として一致し、アイテム区切りとしても使われる(Count = 2) -->
<Message Text="UnescapedReplacedConcatenated3: $(UnescapedReplacedConcatenated3)" />
<Message Text="UnescapedReplacedConcatenated3EqualsConcatenated1: $(UnescapedReplacedConcatenated3EqualsConcatenated1)" />
<Message Text="UnescapedReplacedConcatenated3AsItem.Count: @(UnescapedReplacedConcatenated3AsItem->Count())" />
<!-- この結果は↑と等しい -->
<Message Text="MultipleEscapedProperty: $(MultipleEscapedProperty)" />
<Message Text="UnescapedMultipleEscapedProperty: $(UnescapedMultipleEscapedProperty)" />
<Message Text="MultipleEscapedPropertyAsItem.Count: @(MultipleEscapedPropertyAsItem->Count())" />
<Message Text="UnescapedMultipleEscapedPropertyAsItem.Count: @(UnescapedMultipleEscapedPropertyAsItem->Count())" />
<!-- 確かに等しい判定 -->
<Message Text="MultipleEscapedPropertyEqualsReplacedConcatenated3: $(MultipleEscapedPropertyEqualsReplacedConcatenated3)" />
<!-- 'a%253Bb' != 'a%3Bb'(表示はどちらも a%3Bb / a;b だが内部のエスケープ段数が違う) -->
<Message Text="MultipleEscapedEqualsExplicitlyEscaped: $(MultipleEscapedEqualsExplicitlyEscaped)" />
</Target>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment