-
-
Save fearthecowboy/9e06ad9d92c5d939582147a35c049693 to your computer and use it in GitHub Desktop.
<?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> |
This was a massive help! Whenever i tried running powershell scripts another way notepad just opened with the contents of the script!?!?
I must say that if this approach works (which I assume it does) then I owe you a big thank you. Being able to define the script inside the csproj itself is just pure awesomeness.
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:
<PropertyGroup>
<MyNumber>2</MyNumber>
</PropertyGroup>
<PropertyGroup> <!-- #3 prefix your powershell script with the $(PowerShell) variable, then code as normal! -->
<myscript>$(PowerShell)
.....
$x = 2
If ($x -eq $MyNumber) {
.....
}
.....
</myscript>
</PropertyGroup>
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.
This is amazing. Can you explain the value of the PowerShell property? What does the leading # mean to CMD? And how might one get something like this to work using dotnet core on a non-windows platform (to invoke PowerShell Core from bash platforms?) I have come up with the following which seems close but is giving me strange, erratic errors from PowerShell: