I was attempting to use PowerShell to compile a group of *.cs source files – needing the flexibility of programmatically swapping out dependent assembly references at compile time depending on certain build conditions… Don’t want to get too much in to why I needed it, just that it is doable – (more painful than initially expected), but still possible.
First let’s get a csc command we want to compile.
Second let me state that this was more of an exercise in wanting to learn PowerShell and there probably other ways of accomplishing what I needed, just seemed like a good time to start down the painful learning curve. Also note, I’m not a CSC compiler pro – I haven’t analyzed each of the “options” and weather it’s right/wrong/best practice – it just works… (thanks to Visual Studio & MSBuild for hiding how we actually should use the compiler)
Ok take a simple csc compile command – (In Visual Studio – File –> New Project -> ClassLibrary1 as a good starting point). Compile the project & check the build output window. You’ll get an output similar to the below.
C:\Windows\Microsoft.NET\Framework\v3.5\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.DataSetExtensions.dll" /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll" /debug+ /debug:full /filealign:512 /optimize- /out:obj\Debug\PowershellCscCompileSample.dll /target:library Class1.cs Properties\AssemblyInfo.cs
Next figure how the heck to execute this in PowerShell.
& $csc $params --- NOPE
exec $csc $params – NOPE
I must have tried tens if not hundreds of methods to get the simple thing above to compile… needless to say I pinged a co-worker for some help. http://obsidience.blogspot.com/
His pointer – when trying to get big string command to execute in powershell do the following.
- Open up “Windows PowerShell ISE” (on Windows 7)
- Paste the command prompt window (with an “&” at the beginning)
- look for any coloration changes like…
- Next place PowerShell escape character [`] in front of any character where the coloration changes (They’re very subtle so look long and hard)
We should now have a PowerShell string that compiles our project.
After I got that far – I cleaned up the compiler syntax for a little re-use. (You can download the project blow to check it out)
If you don’t want to see the entire csc compile in the project download above, below is the general usage…
################## Build Configuration ##################
$project_name = 'PowershellCscCompileSample'
$build_configuration = 'Debug'
#########################################################
$core_assemblies_path = 'C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5'
$framework_assemblies_path = 'C:\Windows\Microsoft.NET\Framework\v2.0.50727'
function global:Build-Csc-Command {
param([array]$options, [array]$sourceFiles, [array]$references, [array]$resources)
$csc = 'C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe'
# can't say I'm doing delimeters correctly, but seems to work ???
$delim = [string]""""
$opts = $options
if($references.Count -gt 0)
{
$opts += '/reference:' + $delim + [string]::Join($delim + ' /reference:' + $delim, $references) + $delim
}
if($resources.Count -gt 0)
{
$opts += '/resource:' + $delim + [string]::Join($delim + ' /resource:' + $delim, $resources) + $delim
}
if($sourceFiles.Count -gt 0)
{
$opts += [string]::Join(' ', $sourceFiles)
}
$cmd = [string]::Join(" ", $options)
$cmd = $csc + " " + $opts
$cmd;
}
function global:Execute-Command-String {
param([string]$cmd)
# this drove me crazy... all I wanted to do was execute
# something like this (excluding the [])
#
# [& $csc $opts] OR [& $cmd]
#
# however couldn't figure out the correct powershell syntax...
# But I was able to execute it if I wrote the string out to a
# file and executed it from there... would be nice to not
# have to do that.
$tempFileGuid = ([System.Guid]::NewGuid())
$scriptFile = ".\temp_build_csc_command-$tempFileGuid.ps1"
Remove-If-Exist $scriptFile
Write-Host ''
Write-Host '*********** Executing Command ***********'
Write-Host $cmd
Write-Host '*****************************************'
Write-Host ''
Write-Host ''
$cmd >> $scriptFile
& $scriptFile
Remove-If-Exist $scriptFile
}
function global:Remove-If-Exist {
param($file)
if(Test-Path $file)
{
Remove-Item $file -Force -ErrorAction SilentlyContinue
}
}
$resources = @(
#""
)
$references = @(
"$core_assemblies_path\System.Core.dll",
"$framework_assemblies_path\System.dll"
)
$sourceFiles = @(
#""
)
$sourceFiles += Get-ChildItem '.' -recurse `
| where{$_.Extension -like "*.cs"} `
| foreach {$_.FullName} `
$debug = if($build_configuration.Equals("Release")){ '/debug-'} else{ "/debug+" }
$options = @(
'/noconfig',
'/nowarn:1701`,1702', # Note: the escape [`] character before the comma
'/nostdlib-',
'/errorreport:prompt',
'/warn:4',
$debug,
"/define:$build_configuration``;TRACE", # Note: the escape [`] character before the comma
'/optimize+',
"/out:$project_name\bin\$build_configuration\ClassLibrary.dll",
'/target:library'
)
$cmd = Build-Csc-Command -options $options -sourceFiles $sourceFiles -references $references -resources $resources
Execute-Command-String $cmd