Last active
April 18, 2024 23:37
-
-
Save fearthecowboy/9e06ad9d92c5d939582147a35c049693 to your computer and use it in GitHub Desktop.
The definitive way to use PowerShell from an msbuild script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |
<!-- #1 Place this line at the top of any msbuild script (ie, csproj, etc) --> | |
<PropertyGroup><PowerShell># 2>nul || type %~df0|find /v "setlocal"|find /v "errorlevel"|powershell.exe -noninteractive -& exit %errorlevel% || #</PowerShell></PropertyGroup> | |
<!-- #2 in any target you want to run a script --> | |
<Target Name="default" > | |
<PropertyGroup> <!-- #3 prefix your powershell script with the $(PowerShell) variable, then code as normal! --> | |
<myscript>$(PowerShell) | |
# | |
# powershell script can do whatever you need. | |
# | |
dir ".\*.cs" -recurse |% { | |
write-host Examining file named: $_.FullName | |
# do other stuff here... | |
} | |
$answer = 2+5 | |
write-host Answer is $answer ! | |
</myscript> | |
</PropertyGroup> | |
<!-- #4 and execute the script like this --> | |
<Exec Command="$(myscript)" EchoOff="true" /> | |
<!-- | |
Notes: | |
====== | |
- You can still use the standard Exec Task features! (see: https://msdn.microsoft.com/en-us/library/x8zx72cd.aspx) | |
- if your powershell script needs to use < > or & characters, just place the contents in a CDATA wrapper: | |
<script2><![CDATA[ $(PowerShell) | |
# your powershell code goes here! | |
write-host "<<Hi mom!>>" | |
]]></script2> | |
- if you want return items to the msbuild script you can get them: | |
<script3>$(PowerShell) | |
# your powershell code goes here! | |
(dir "*.cs" -recurse).FullName | |
</script3> | |
<Exec Command="$(script3)" EchoOff="true" ConsoleToMSBuild="true"> | |
<Output TaskParameter="ConsoleOutput" PropertyName="items" /> | |
</Exec> | |
<Touch Files="$(items)" /> <- see! then you can use those items with another msbuild Task | |
--> | |
</Target> | |
</Project> |
did you try $(MyNumber)
? IIRC that should work
I found this to be extremely helpful ! However, I ran through some limitations with the PowerShell command. Those limitations are:
- It fails if the PowerShell script is inlined in the
Exec
task. e.g.<Exec Command="$(PowerShell)" Write-Host "ok"" />
- The actual return code of PowerShell is not returned. This is a limitation of the Windows Command Line. With the
& exit %errorlevel%
on the same line as the PowerShell command,0
is always returned even ifExit -1
is in the PowerShell script. In order for the PowerShell process return code to be returned to Visual Studio, theexit %errorlevel%
must be on a separate line. - It fails if the PowerShell script declared in the property ends with
last powershell command ]]
(where]]
are the closing braces of the![CDATA[
tag on the same line as the last PowerShell command). - If the PowerShell script is a function ending with a call to that function and that function has required parameters, the PowerShell command fails if an explicit
%0D%0A
is not added at the end of the command. e.g.<Exec Command="$(PowerShell) $(ScriptWithFunctionRequiringParameters) "@(ItemGroupUsedAsParameter)" %0D%0A" />
Now, that being said, I used your scheme and changed the find /v
commands with PowerShell -replace
commands which allow using regular expressions. What I came up with is:
<PropertyGroup>
<PowerShellCommand>
powershell.exe -Command "&{((Get-Content \"%~df0\" -Raw) -replace \"(?sm).*:START_POWERSHELL_SCRIPT\", \"\" -replace \"exit %%.*%%\", \"\") + \"`n\"}"|powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -
exit %ERRORLEVEL%
:START_POWERSHELL_SCRIPT
%0D%0A
</PowerShellCommand>
</PropertyGroup>
With this improved PowerShell command, I was able to fix all the limitations previously mentioned, I thought I'd share it with you since the original command found here really helped be grasp how MsBuild was actually calling PowerShell.
<Target Name="Trigger" AfterTargets="Generate" >
<PropertyGroup>
<PowerShellCommand Condition=" '$(OS)' == 'Windows_NT' " >powershell</PowerShellCommand>
<PowerShellCommand Condition=" '$(OS)' == 'Unix' " >pwsh</PowerShellCommand>
<ClientGenTriggerScript>$(MSBuildProjectDirectory)\..\..\..\.build\client_gen.ps1</ClientGenTriggerScript>
</PropertyGroup>
<Exec Command="$(PowerShellCommand) -ExecutionPolicy Unrestricted -NoProfile -File $(ClientGenTriggerScript) -parameter1 p1 -parameter2 $(p2) --Verbose" />
</Target>
I use it this way.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is great - do you know how to reference other variables from within the script? For example, accessing the
MyNumber
property from within the script: