Developing on Staxmanade

PowerShell – Compiling with csc.exe – more of a headache that it should have been. It is possible…

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.

  1. Open up “Windows PowerShell ISE”  (on Windows 7)
  2. Paste the command prompt window (with an “&” at the beginning)
  3. look for any coloration changes like…
     image
  4. Next place PowerShell escape character [`] in front of any character where the coloration changes (They’re very subtle so look long and hard)
     image

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