The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 Windows PowerShell 輸出多行文字應注意的斷行陷阱

我今天遇到了一個棘手問題,原本想透過 PowerShell 寫入一個多行的文字,並放入 Azure Pipelines 執行。這樣的需求我寫過很多次了,不過當下的我突然想不起來確切的語法,所以上網 Google 找到了 PowerShell Multiline String 這篇文章,看到了一個解決方案,當下沒有多想,複製貼上就套用了,而且測試過確實有效,接著就是一連串的鬼打牆,浪費了寶貴的半小時生命。

正確的多行文字字串語法

由於我要輸出的檔案內容是 Web.config 檔,這是一份 XML 格式的檔案,所以若用字串串接的話,要靠 ` 跳脫,所以非常不方便,可讀性也差,因此我直接跳過這個選項。

以下則是我認為最正確的用法,這種語法的名稱叫做 Here-strings

  1. 完整字串輸出的範本字串語法

    記得開頭第一行必須為 @' 且結尾行必須為 '@ 才是正確語法!

    @'
    first line
    $(Get-Date)
    second line
    '@
    

    輸出內容如下:

    first line
    $(Get-Date)
    second line
    
  2. 支援變數內插的範本字串語法

    記得開頭第一行必須為 @" 且結尾行必須為 "@ 才是正確語法!

    @"
    first line
    $(Get-Date)
    second line
    "@
    

    輸出內容如下:

    first line
    01/25/2022 23:08:25
    second line
    

悲劇的錯誤用法

我今天不知道哪根筋不對,上網抄了一個我之前沒用過的語法,我有簡易測試過有效,誰知道有地雷!🔥

今天錯誤的語法如下:

{
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\MyProject.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
  </location>
</configuration>
 } | Out-File 'Web.config' -Encoding UTF8

在我的本機測試,輸出的 Web.config 完全沒問題,一開始不知道為何,只要在 Azure Pipelines 上面,輸出的 Web.config 就會被加入詭異的斷行字元,非常的詭異,一開始真的很難意識到有這個問題,只覺得為什麼部署到客戶的 IIS 就是無法正常運行!

經過多次嘗試,這才發現我輸出的 Web.config 檔案內容如下,有兩行的語法是無效的,被插入了額外的斷行符號:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" re
sourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\MyProject.dll" stdoutLogEn
abled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
  </location>
</configuration>

當下看到真的是 WTF 😡

我在本機測試過很多次,就是無法重現此問題。後來想到或許是 Windows Terminal 的關係?我便改用 Windows PowerShell 來執行同一段語法,重點是,我依然無法重現問題,真的非常詭異!

由於輸出的文字被強迫斷行,我就懷疑可能跟視窗大小有關,我把 Windows Terminal 視窗調的很窄,重跑一次,依然沒有問題。接著我換 Windows PowerShell 再次執行同一段語法,但是視窗拉的很窄,這時出現以下結果:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetC
oreModule" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\MyProject.dll
" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
  </location>
</configuration>

這下終於被我確認出,原來只有在 Windows PowerShell 執行上述命令,才會有被強迫斷行的問題。而我 Azure Pipelines 的 Agents 是跑在 Windows Server 2019 上,預設也是跑在 Windows PowerShell 下,即便 Windows PowerShell 是跑在背景,但還是有個預設寬度,所以跑這段程式時過長的行會直接被截斷,真是意外中的意外,完全出乎我的意料之外,太奇葩、太罕見了!

這篇文章寫了一個根本不該這樣使用的語法當範例,事後真的很想當面罵他髒話,根本教壞小朋友,文章亂寫一通。(怒)

我後來理解到這個特殊的語法,其實是一段 ScriptBlock,原始的目的根本不是用來當成字串用的,請不要把這種程式碼當成範例來寫! (怒氣未消)

Windows PowerShell - ScriptBlock

最後再次強調,正確的語法如下:

@'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\MyProject.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
    </system.webServer>
  </location>
</configuration>
'@ | Out-File 'Web.config' -Encoding UTF8

我想我以後不會再忘記了! 😁

相關連結