All files
[pithos-ms-client] / trunk / Libraries / Json40r2 / Source / Tools / PSake / psake.psm1
1 # psake
2 # Copyright (c) 2010 James Kovacs
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to deal
5 # in the Software without restriction, including without limitation the rights
6 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 # copies of the Software, and to permit persons to whom the Software is
8 # furnished to do so, subject to the following conditions:
9 #
10 # The above copyright notice and this permission notice shall be included in
11 # all copies or substantial portions of the Software.
12 #
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 # THE SOFTWARE.
20
21 #Requires -Version 2.0
22
23 #-- Private Module Variables (Listed here for quick reference)
24 [system.collections.stack]$script:context
25
26 #-- Public Module Variables -- The psake hashtable variable is initialized in the invoke-psake function
27 $script:psake = @{}
28 $script:psake.use_exit_on_error = $false    # determines if psake uses the "exit()" function when an exception occurs
29 $script:psake.log_error = $false            # determines if the exception details are written to a file
30 $script:psake.build_success = $false        # indicates that the current build was successful
31 $script:psake.version = "4.00"              # contains the current version of psake
32 $script:psake.build_script_file = $null     # contains a System.IO.FileInfo for the current build file
33 $script:psake.framework_version = ""        # contains the framework version # for the current build
34 $script:psake.default_build_file_name = 'default.ps1'
35
36 Export-ModuleMember -Variable "psake"
37
38 #-- Private Module Functions
39 function ExecuteTask
40 {
41   param([string]$taskName)
42
43   Assert (![string]::IsNullOrEmpty($taskName)) "Task name should not be null or empty string"
44
45   $taskKey = $taskName.ToLower()
46
47     Assert ($script:context.Peek().tasks.Contains($taskKey)) "task [$taskName] does not exist"
48
49     if ($script:context.Peek().executedTasks.Contains($taskKey))
50   {
51     return
52   }
53
54   Assert (!$script:context.Peek().callStack.Contains($taskKey)) "Error: Circular reference found for task, $taskName"
55
56   $script:context.Peek().callStack.Push($taskKey)
57
58   $task = $script:context.Peek().tasks.$taskKey
59
60   $taskName = $task.Name
61
62   $precondition_is_valid = if ($task.Precondition -ne $null) {& $task.Precondition} else {$true}
63
64   if (!$precondition_is_valid)
65   {
66     "Precondition was false not executing $name"
67   }
68   else
69   {
70     if ($taskKey -ne 'default')
71     {
72       $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
73
74       if ( ($task.PreAction -ne $null) -or ($task.PostAction -ne $null) )
75       {
76         Assert ($task.Action -ne $null) "Error: Action parameter must be specified when using PreAction or PostAction parameters"
77       }
78
79       if ($task.Action -ne $null)
80       {
81         try
82         {
83           foreach($childTask in $task.DependsOn)
84           {
85             ExecuteTask $childTask
86           }
87
88           $script:context.Peek().currentTaskName = $taskName
89
90           if ($script:context.Peek().taskSetupScriptBlock -ne $null)
91           {
92             & $script:context.Peek().taskSetupScriptBlock
93           }
94
95           if ($task.PreAction -ne $null)
96           {
97             & $task.PreAction
98           }
99
100           $script:context.Peek().formatTaskNameString -f $taskName
101           & $task.Action
102
103           if ($task.PostAction -ne $null)
104           {
105             & $task.PostAction
106           }
107
108           if ($script:context.Peek().taskTearDownScriptBlock -ne $null)
109           {
110             & $script:context.Peek().taskTearDownScriptBlock
111           }
112         }
113         catch
114         {
115           if ($task.ContinueOnError)
116           {
117             "-"*70
118             "Error in Task [$taskName] $_"
119             "-"*70
120           }
121           else
122           {
123             throw $_
124           }
125         }
126       } # if ($task.Action -ne $null)
127       else
128       {
129         #no Action was specified but we still execute all the dependencies
130         foreach($childTask in $task.DependsOn)
131         {
132           ExecuteTask $childTask
133         }
134       }
135       $stopwatch.stop()
136       $task.Duration = $stopwatch.Elapsed
137     } # if ($name.ToLower() -ne 'default')
138     else
139     {
140       foreach($childTask in $task.DependsOn)
141       {
142         ExecuteTask $childTask
143       }
144     }
145
146     if ($task.Postcondition -ne $null)
147     {
148       Assert (& $task.Postcondition) "Error: Postcondition failed for $taskName"
149     }
150   }
151
152   $poppedTaskKey = $script:context.Peek().callStack.Pop()
153
154   Assert ($poppedTaskKey -eq $taskKey) "Error: CallStack was corrupt. Expected $taskKey, but got $poppedTaskKey."
155
156   $script:context.Peek().executedTasks.Push($taskKey)
157 }
158
159 function Configure-BuildEnvironment
160 {
161   if ($framework.Length -ne 3 -and $framework.Length -ne 6) {
162     throw "Error: Invalid .NET Framework version, $framework, specified"
163   }
164   $versionPart = $framework.Substring(0,3)
165   $bitnessPart = $framework.Substring(3)
166   $versions = $null
167   switch ($versionPart)
168   {
169     '1.0' { $versions = @('v1.0.3705')  }
170     '1.1' { $versions = @('v1.1.4322')  }
171     '2.0' { $versions = @('v2.0.50727') }
172     '3.0' { $versions = @('v2.0.50727') }
173     '3.5' { $versions = @('v3.5','v2.0.50727') }
174     '4.0' { $versions = @('v4.0.30319') }
175     default { throw "Error: Unknown .NET Framework version, $versionPart, specified in $framework" }
176   }
177
178   $bitness = 'Framework'
179   if($versionPart -ne '1.0' -and $versionPart -ne '1.1') {
180     switch ($bitnessPart)
181     {
182       'x86' { $bitness = 'Framework' }
183       'x64' { $bitness = 'Framework64' }
184       $null {
185         $ptrSize = [System.IntPtr]::Size
186         switch ($ptrSize)
187         {
188           4 { $bitness = 'Framework' }
189           8 { $bitness = 'Framework64' }
190           default { throw "Error: Unknown pointer size ($ptrSize) returned from System.IntPtr." }
191         }
192       }
193       default { throw "Error: Unknown .NET Framework bitness, $bitnessPart, specified in $framework" }
194     }
195   }
196   $frameworkDirs = $versions | foreach { "$env:windir\Microsoft.NET\$bitness\$_\" }
197
198   $frameworkDirs | foreach { Assert (test-path $_) "Error: No .NET Framework installation directory found at $_" }
199
200   $env:path = [string]::Join(';', $frameworkDirs) + ";$env:path"
201   #if any error occurs in a PS function then "stop" processing immediately
202   # this does not effect any external programs that return a non-zero exit code
203   $global:ErrorActionPreference = "Stop"
204 }
205
206 function Cleanup-Environment
207 {
208   $env:path = $script:context.Peek().originalEnvPath
209   Set-Location $script:context.Peek().originalDirectory
210   $global:ErrorActionPreference = $script:context.Peek().originalErrorActionPreference
211 }
212
213 #borrowed from Jeffrey Snover http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx
214 function Resolve-Error($ErrorRecord=$Error[0])
215 {
216   "ErrorRecord"
217   $ErrorRecord | Format-List * -Force | Out-String -Stream | ? {$_}
218   ""
219   "ErrorRecord.InvocationInfo"
220   $ErrorRecord.InvocationInfo | Format-List * | Out-String -Stream | ? {$_}
221   ""
222   "Exception"
223   $Exception = $ErrorRecord.Exception
224   for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
225   {
226     "$i" * 70
227     $Exception | Format-List * -Force | Out-String -Stream | ? {$_}
228     ""
229   }
230 }
231
232 function Write-Documentation
233 {
234   $list = New-Object System.Collections.ArrayList
235   foreach($key in $script:context.Peek().tasks.Keys)
236   {
237     if($key -eq "default")
238     {
239       continue
240     }
241     $task = $script:context.Peek().tasks.$key
242     $content = "" | Select-Object Name, Description
243     $content.Name = $task.Name
244     $content.Description = $task.Description
245     $index = $list.Add($content)
246   }
247
248   $list | Sort 'Name' | Format-Table -Auto
249 }
250
251 function Write-TaskTimeSummary
252 {
253   "-"*70
254   "Build Time Report"
255   "-"*70
256   $list = @()
257   while ($script:context.Peek().executedTasks.Count -gt 0)
258   {
259     $taskKey = $script:context.Peek().executedTasks.Pop()
260     $task = $script:context.Peek().tasks.$taskKey
261     if($taskKey -eq "default")
262     {
263       continue
264     }
265     $list += "" | Select-Object @{Name="Name";Expression={$task.Name}}, @{Name="Duration";Expression={$task.Duration}}
266   }
267   [Array]::Reverse($list)
268   $list += "" | Select-Object @{Name="Name";Expression={"Total:"}}, @{Name="Duration";Expression={$stopwatch.Elapsed}}
269   $list | Format-Table -Auto | Out-String -Stream | ? {$_}  # using "Out-String -Stream" to filter out the blank line that Format-Table prepends
270 }
271
272 #-- Public Module Functions
273 function Exec
274 {
275 <#
276 .SYNOPSIS
277 Helper function for executing command-line programs.
278
279 .DESCRIPTION
280 This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode to see if an error occcured.
281 If an error is detected then an exception is thrown.  This function allows you to run command-line programs without
282 having to explicitly check fthe $lastexitcode variable.
283
284 .PARAMETER cmd
285 The scriptblock to execute.  This scriptblock will typically contain the command-line invocation.
286 Required
287
288 .PARAMETER errorMessage
289 The error message used for the exception that is thrown.
290 Optional
291
292 .EXAMPLE
293 exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
294
295 This example calls the svn command-line client.
296
297 .LINK
298 Assert
299 Invoke-psake
300 Task
301 Properties
302 Include
303 FormatTaskName
304 TaskSetup
305 TaskTearDown
306 #>
307 [CmdletBinding(
308     SupportsShouldProcess=$False,
309     SupportsTransactions=$False,
310     ConfirmImpact="None",
311     DefaultParameterSetName="")]
312
313   param(
314       [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
315     [Parameter(Position=1,Mandatory=0)][string]$errorMessage = "Error executing command: " + $cmd
316   )
317      & $cmd
318      if ($lastexitcode -ne 0)
319      {
320           throw $errorMessage
321      }
322 }
323
324 function Assert
325 {
326 <#
327 .SYNOPSIS
328 Helper function for "Design by Contract" assertion checking.
329
330 .DESCRIPTION
331 This is a helper function that makes the code less noisy by eliminating many of the "if" statements
332 that are normally required to verify assumptions in the code.
333
334 .PARAMETER conditionToCheck
335 The boolean condition to evaluate
336 Required
337
338 .PARAMETER failureMessage
339 The error message used for the exception if the conditionToCheck parameter is false
340 Required
341
342 .EXAMPLE
343 Assert $false "This always throws an exception"
344
345 This example always throws an exception
346
347 .EXAMPLE
348 Assert ( ($i % 2) -eq 0 ) "%i is not an even number"
349
350 This exmaple may throw an exception if $i is not an even number
351
352 .LINK
353 Invoke-psake
354 Task
355 Properties
356 Include
357 FormatTaskName
358 TaskSetup
359 TaskTearDown
360
361 .NOTES
362 It might be necessary to wrap the condition with paranthesis to force PS to evaluate the condition
363 so that a boolean value is calculated and passed into the 'conditionToCheck' parameter.
364
365 Example:
366     Assert 1 -eq 2 "1 doesn't equal 2"
367
368 PS will pass 1 into the condtionToCheck variable and PS will look for a parameter called "eq" and
369 throw an exception with the following message "A parameter cannot be found that matches parameter name 'eq'"
370
371 The solution is to wrap the condition in () so that PS will evaluate it first.
372
373     Assert (1 -eq 2) "1 doesn't equal 2"
374 #>
375 [CmdletBinding(
376     SupportsShouldProcess=$False,
377     SupportsTransactions=$False,
378     ConfirmImpact="None",
379     DefaultParameterSetName="")]
380
381   param(
382     [Parameter(Position=0,Mandatory=1)]$conditionToCheck,
383     [Parameter(Position=1,Mandatory=1)]$failureMessage
384   )
385   if (!$conditionToCheck) { throw $failureMessage }
386 }
387
388 function Task
389 {
390 <#
391 .SYNOPSIS
392 Defines a build task to be executed by psake
393
394 .DESCRIPTION
395 This function creates a 'task' object that will be used by the psake engine to execute a build task.
396 Note: There must be at least one task called 'default' in the build script
397
398 .PARAMETER Name
399 The name of the task
400 Required
401
402 .PARAMETER Action
403 A scriptblock containing the statements to execute
404 Optional
405
406 .PARAMETER PreAction
407 A scriptblock to be executed before the 'Action' scriptblock.
408 Note: This parameter is ignored if the 'Action' scriptblock is not defined.
409 Optional
410
411 .PARAMETER PostAction
412 A scriptblock to be executed after the 'Action' scriptblock.
413 Note: This parameter is ignored if the 'Action' scriptblock is not defined.
414 Optional
415
416 .PARAMETER Precondition
417 A scriptblock that is executed to determine if the task is executed or skipped.
418 This scriptblock should return $true or $false
419 Optional
420
421 .PARAMETER Postcondition
422 A scriptblock that is executed to determine if the task completed its job correctly.
423 An exception is thrown if the scriptblock returns $false.
424 Optional
425
426 .PARAMETER ContinueOnError
427 If this switch parameter is set then the task will not cause the build to fail when an exception is thrown
428
429 .PARAMETER Depends
430 An array of tasks that this task depends on.  They will be executed before the current task is executed.
431
432 .PARAMETER Description
433 A description of the task.
434
435 .EXAMPLE
436 A sample build script is shown below:
437
438 task default -depends Test
439
440 task Test -depends Compile, Clean {
441   "This is a test"
442 }
443
444 task Compile -depends Clean {
445   "Compile"
446 }
447
448 task Clean {
449   "Clean"
450 }
451
452 The 'default' task is required and should not contain an 'Action' parameter.
453 It uses the 'depends' parameter to specify that 'Test' is a dependency
454
455 The 'Test' task uses the 'depends' parameter to specify that 'Compile' and 'Clean' are dependencies
456 The 'Compile' task depends on the 'Clean' task.
457
458 Note:
459 The 'Action' parameter is defaulted to the script block following the 'Clean' task.
460
461 The equivalent 'Test' task is shown below:
462
463 task Test -depends Compile, Clean -Action {
464   $testMessage
465 }
466
467 The output for the above sample build script is shown below:
468 Executing task, Clean...
469 Clean
470 Executing task, Compile...
471 Compile
472 Executing task, Test...
473 This is a test
474
475 Build Succeeded!
476
477 ----------------------------------------------------------------------
478 Build Time Report
479 ----------------------------------------------------------------------
480 Name    Duration
481 ----    --------
482 Clean   00:00:00.0065614
483 Compile 00:00:00.0133268
484 Test    00:00:00.0225964
485 Total:  00:00:00.0782496
486
487 .LINK
488 Invoke-psake
489 Properties
490 Include
491 FormatTaskName
492 TaskSetup
493 TaskTearDown
494 Assert
495 #>
496 [CmdletBinding(
497     SupportsShouldProcess=$False,
498     SupportsTransactions=$False,
499     ConfirmImpact="None",
500     DefaultParameterSetName="")]
501   param(
502     [Parameter(Position=0,Mandatory=1)]
503     [string]$name = $null,
504     [Parameter(Position=1,Mandatory=0)]
505     [scriptblock]$action = $null,
506     [Parameter(Position=2,Mandatory=0)]
507     [scriptblock]$preaction = $null,
508     [Parameter(Position=3,Mandatory=0)]
509     [scriptblock]$postaction = $null,
510     [Parameter(Position=4,Mandatory=0)]
511     [scriptblock]$precondition = $null,
512     [Parameter(Position=5,Mandatory=0)]
513     [scriptblock]$postcondition = $null,
514     [Parameter(Position=6,Mandatory=0)]
515     [switch]$continueOnError = $false,
516     [Parameter(Position=7,Mandatory=0)]
517     [string[]]$depends = @(),
518     [Parameter(Position=8,Mandatory=0)]
519     [string]$description = $null
520     )
521
522   if ($name.ToLower() -eq 'default')
523   {
524     Assert ($action -eq $null) "Error: 'default' task cannot specify an action"
525   }
526
527   $newTask = @{
528     Name = $name
529     DependsOn = $depends
530     PreAction = $preaction
531     Action = $action
532     PostAction = $postaction
533     Precondition = $precondition
534     Postcondition = $postcondition
535     ContinueOnError = $continueOnError
536     Description = $description
537     Duration = 0
538   }
539
540   $taskKey = $name.ToLower()
541
542   Assert (!$script:context.Peek().tasks.ContainsKey($taskKey)) "Error: Task, $name, has already been defined."
543
544   $script:context.Peek().tasks.$taskKey = $newTask
545 }
546
547 function Properties
548 {
549 <#
550 .SYNOPSIS
551 Define a scriptblock that contains assignments to variables that will be available to all tasks in the build script
552
553 .DESCRIPTION
554 A build script may declare a "Properies" function which allows you to define
555 variables that will be available to all the "Task" functions in the build script.
556
557 .PARAMETER properties
558 The script block containing all the variable assignment statements
559 Required
560
561 .EXAMPLE
562 A sample build script is shown below:
563
564 Properties {
565   $build_dir = "c:\build"
566   $connection_string = "datasource=localhost;initial catalog=northwind;integrated security=sspi"
567 }
568
569 Task default -depends Test
570
571 Task Test -depends Compile, Clean {
572 }
573
574 Task Compile -depends Clean {
575 }
576
577 Task Clean {
578 }
579
580 .LINK
581 Invoke-psake
582 Task
583 Include
584 FormatTaskName
585 TaskSetup
586 TaskTearDown
587 Assert
588
589 .NOTES
590 You can have more than 1 "Properties" function defined in the script
591 #>
592 [CmdletBinding(
593     SupportsShouldProcess=$False,
594     SupportsTransactions=$False,
595     ConfirmImpact="None",
596     DefaultParameterSetName="")]
597   param(
598   [Parameter(Position=0,Mandatory=1)]
599   [scriptblock]$properties
600   )
601   $script:context.Peek().properties += $properties
602 }
603
604 function Include
605 {
606 <#
607 .SYNOPSIS
608 Include the functions or code of another powershell script file into the current build script's scope
609
610 .DESCRIPTION
611 A build script may declare an "includes" function which allows you to define
612 a file containing powershell code to be included and added to the scope of
613 the currently running build script.
614
615 .PARAMETER fileNamePathToInclude
616 A string containing the path and name of the powershell file to include
617 Required
618
619 .EXAMPLE
620 A sample build script is shown below:
621
622 Include ".\build_utils.ps1"
623
624 Task default -depends Test
625
626 Task Test -depends Compile, Clean {
627 }
628
629 Task Compile -depends Clean {
630 }
631
632 Task Clean {
633 }
634
635
636 .LINK
637 Invoke-psake
638 Task
639 Properties
640 FormatTaskName
641 TaskSetup
642 TaskTearDown
643 Assert
644
645 .NOTES
646 You can have more than 1 "Include" function defined in the script
647 #>
648 [CmdletBinding(
649     SupportsShouldProcess=$False,
650     SupportsTransactions=$False,
651     ConfirmImpact="None",
652     DefaultParameterSetName="")]
653   param(
654   [Parameter(Position=0,Mandatory=1)]
655   [string]$fileNamePathToInclude
656   )
657   Assert (test-path $fileNamePathToInclude) "Error: Unable to include $fileNamePathToInclude. File not found."
658   $script:context.Peek().includes.Enqueue((Resolve-Path $fileNamePathToInclude));
659 }
660
661 function FormatTaskName
662 {
663 <#
664 .SYNOPSIS
665 Allows you to define a format mask that will be used when psake displays
666 the task name
667
668 .DESCRIPTION
669 Allows you to define a format mask that will be used when psake displays
670 the task name.  The default is "Executing task, {0}..."
671
672 .PARAMETER format
673 A string containing the format mask to use, it should contain a placeholder ({0})
674 that will be used to substitute the task name.
675 Required
676
677 .EXAMPLE
678 A sample build script is shown below:
679
680 FormatTaskName "[Task: {0}]"
681
682 Task default -depends Test
683
684 Task Test -depends Compile, Clean {
685 }
686
687 Task Compile -depends Clean {
688 }
689
690 Task Clean {
691 }
692
693 You should get the following output:
694 ------------------------------------
695
696 [Task: Clean]
697 [Task: Compile]
698 [Task: Test]
699
700 Build Succeeded
701
702 ----------------------------------------------------------------------
703 Build Time Report
704 ----------------------------------------------------------------------
705 Name    Duration
706 ----    --------
707 Clean   00:00:00.0043477
708 Compile 00:00:00.0102130
709 Test    00:00:00.0182858
710 Total:  00:00:00.0698071
711
712 .LINK
713 Invoke-psake
714 Include
715 Task
716 Properties
717 TaskSetup
718 TaskTearDown
719 Assert
720 #>
721 [CmdletBinding(
722     SupportsShouldProcess=$False,
723     SupportsTransactions=$False,
724     ConfirmImpact="None",
725     DefaultParameterSetName="")]
726   param(
727   [Parameter(Position=0,Mandatory=1)]
728   [string]$format
729   )
730   $script:context.Peek().formatTaskNameString = $format
731 }
732
733 function TaskSetup
734 {
735 <#
736 .SYNOPSIS
737 Adds a scriptblock that will be executed before each task
738
739 .DESCRIPTION
740 This function will accept a scriptblock that will be executed before each
741 task in the build script.
742
743 .PARAMETER include
744 A scriptblock to execute
745 Required
746
747 .EXAMPLE
748 A sample build script is shown below:
749
750 Task default -depends Test
751
752 Task Test -depends Compile, Clean {
753 }
754
755 Task Compile -depends Clean {
756 }
757
758 Task Clean {
759 }
760
761 TaskSetup {
762   "Running 'TaskSetup' for task $script:context.Peek().currentTaskName"
763 }
764
765 You should get the following output:
766 ------------------------------------
767
768 Running 'TaskSetup' for task Clean
769 Executing task, Clean...
770 Running 'TaskSetup' for task Compile
771 Executing task, Compile...
772 Running 'TaskSetup' for task Test
773 Executing task, Test...
774
775 Build Succeeded
776
777 ----------------------------------------------------------------------
778 Build Time Report
779 ----------------------------------------------------------------------
780 Name    Duration
781 ----    --------
782 Clean   00:00:00.0054018
783 Compile 00:00:00.0123085
784 Test    00:00:00.0236915
785 Total:  00:00:00.0739437
786
787 .LINK
788 Invoke-psake
789 Include
790 Task
791 Properties
792 FormatTaskName
793 TaskTearDown
794 Assert
795 #>
796 [CmdletBinding(
797     SupportsShouldProcess=$False,
798     SupportsTransactions=$False,
799     ConfirmImpact="None",
800     DefaultParameterSetName="")]
801   param(
802   [Parameter(Position=0,Mandatory=1)]
803   [scriptblock]$setup
804   )
805   $script:context.Peek().taskSetupScriptBlock = $setup
806 }
807
808 function TaskTearDown
809 {
810 <#
811 .SYNOPSIS
812 Adds a scriptblock that will be executed after each task
813
814 .DESCRIPTION
815 This function will accept a scriptblock that will be executed after each
816 task in the build script.
817
818 .PARAMETER include
819 A scriptblock to execute
820 Required
821
822 .EXAMPLE
823 A sample build script is shown below:
824
825 Task default -depends Test
826
827 Task Test -depends Compile, Clean {
828 }
829
830 Task Compile -depends Clean {
831 }
832
833 Task Clean {
834 }
835
836 TaskTearDown {
837   "Running 'TaskTearDown' for task $script:context.Peek().currentTaskName"
838 }
839
840 You should get the following output:
841 ------------------------------------
842
843 Executing task, Clean...
844 Running 'TaskTearDown' for task Clean
845 Executing task, Compile...
846 Running 'TaskTearDown' for task Compile
847 Executing task, Test...
848 Running 'TaskTearDown' for task Test
849
850 Build Succeeded
851
852 ----------------------------------------------------------------------
853 Build Time Report
854 ----------------------------------------------------------------------
855 Name    Duration
856 ----    --------
857 Clean   00:00:00.0064555
858 Compile 00:00:00.0218902
859 Test    00:00:00.0309151
860 Total:  00:00:00.0858301
861
862 .LINK
863 Invoke-psake
864 Include
865 Task
866 Properties
867 FormatTaskName
868 TaskSetup
869 Assert
870 #>
871 [CmdletBinding(
872     SupportsShouldProcess=$False,
873     SupportsTransactions=$False,
874     ConfirmImpact="None",
875     DefaultParameterSetName="")]
876   param(
877   [Parameter(Position=0,Mandatory=1)]
878   [scriptblock]$teardown)
879   $script:context.Peek().taskTearDownScriptBlock = $teardown
880 }
881
882 function Invoke-psake
883 {
884 <#
885 .SYNOPSIS
886 Runs a psake build script.
887
888 .DESCRIPTION
889 This function runs a psake build script
890
891 .PARAMETER BuildFile
892 The psake build script to execute (default: default.ps1).
893
894 .PARAMETER TaskList
895 A comma-separated list of task names to execute
896
897 .PARAMETER Framework
898 The version of the .NET framework you want to build. You can append x86 or x64 to force a specific framework. If not specified, x86 or x64 will be detected based on the bitness of the PowerShell process.
899 Possible values: '1.0', '1.1', '2.0', '2.0x86', '2.0x64', '3.0', '3.0x86', '3.0x64', '3.5', '3.5x86', '3.5x64', '4.0', '4.0x86', '4.0x64'
900 Default = '3.5'
901
902 .PARAMETER Docs
903 Prints a list of tasks and their descriptions
904
905 .PARAMETER Parameters
906 A hashtable containing parameters to be passed into the current build script.  These parameters will be processed before the 'Properties' function of the script is processed.  This means you can access parameters from within the 'Properties' function!
907
908 .PARAMETER Properties
909 A hashtable containing properties to be passed into the current build script.  These properties will override matching properties that are found in the 'Properties' function of the script.
910
911 .EXAMPLE
912 Invoke-psake
913
914 Runs the 'default' task in the 'default.ps1' build script in the current directory
915
916 .EXAMPLE
917 Invoke-psake '.\build.ps1'
918
919 Runs the 'default' task in the '.build.ps1' build script
920
921 .EXAMPLE
922 Invoke-psake '.\build.ps1' Tests,Package
923
924 Runs the 'Tests' and 'Package' tasks in the '.build.ps1' build script
925
926 .EXAMPLE
927 Invoke-psake Tests
928
929 If you have your Tasks in the .\default.ps1. This example will run the 'Tests' tasks in the 'default.ps1' build script.
930
931 .EXAMPLE
932 Invoke-psake 'Tests, Package'
933
934 If you have your Tasks in the .\default.ps1. This example will run the 'Tests' and 'Package' tasks in the 'default.ps1' build script.
935 NOTE: the quotes around the list of tasks to execute.
936
937 .EXAMPLE
938 Invoke-psake '.\build.ps1' -docs
939
940 Prints a report of all the tasks and their descriptions and exits
941
942 .EXAMPLE
943 Invoke-psake .\parameters.ps1 -parameters @{"p1"="v1";"p2"="v2"}
944
945 Runs the build script called 'parameters.ps1' and passes in parameters 'p1' and 'p2' with values 'v1' and 'v2'
946
947 .EXAMPLE
948 Invoke-psake .\properties.ps1 -properties @{"x"="1";"y"="2"}
949
950 Runs the build script called 'properties.ps1' and passes in parameters 'x' and 'y' with values '1' and '2'
951
952 .OUTPUTS
953     If there is an exception and '$psake.use_exit_on_error' -eq $true
954   then runs exit(1) to set the DOS lastexitcode variable
955   otherwise set the '$psake.build_success variable' to $true or $false depending
956   on whether an exception was thrown
957
958 .NOTES
959 When the psake module is loaded a variabled called $psake is created it is a hashtable
960 containing some variables that can be used to configure psake:
961
962 $psake.use_exit_on_error = $false   # determines if psake uses the "exit()" function when an exception occurs
963 $psake.log_error = $false           # determines if the exception details are written to a file
964 $psake.build_success = $false       # indicates that the current build was successful
965 $psake.version = "4.00"             # contains the current version of psake
966 $psake.build_script_file = $null    # contains a System.IO.FileInfo for the current build file
967 $psake.framework_version = ""       # contains the framework version # for the current build
968
969 $psake.use_exit_on_error and $psake.log_error are boolean variables that can be set before you call Invoke-Psake.
970
971 You should see the following when you display the contents of the $psake variable right after importing psake
972
973 PS projects:\psake> Import-Module .\psake.psm1
974 PS projects:\psake> $psake
975
976 Name                           Value
977 ----                           -----
978 version                        4.00
979 build_script_file
980 use_exit_on_error              False
981 build_success                  False
982 log_error                      False
983 framework_version
984
985 After a build is executed the following $psake values are updated (build_script_file, build_success, and framework_version)
986
987 PS projects:\psake> Invoke-psake .\examples\default.ps1
988 Executing task: Clean
989 Executed Clean!
990 Executing task: Compile
991 Executed Compile!
992 Executing task: Test
993 Executed Test!
994
995 Build Succeeded!
996
997 ----------------------------------------------------------------------
998 Build Time Report
999 ----------------------------------------------------------------------
1000 Name    Duration
1001 ----    --------
1002 Clean   00:00:00.0798486
1003 Compile 00:00:00.0869948
1004 Test    00:00:00.0958225
1005 Total:  00:00:00.2712414
1006
1007 PS projects:\psake> $psake
1008
1009 Name                           Value
1010 ----                           -----
1011 version                        4.00
1012 build_script_file              C:\Users\Jorge\Documents\Projects\psake\examples\default.ps1
1013 use_exit_on_error              False
1014 build_success                  True
1015 log_error                      False
1016 framework_version              3.5
1017
1018 .LINK
1019 Task
1020 Include
1021 Properties
1022 FormatTaskName
1023 TaskSetup
1024 TaskTearDown
1025 Assert
1026 #>
1027 [CmdletBinding(
1028     SupportsShouldProcess=$False,
1029     SupportsTransactions=$False,
1030     ConfirmImpact="None",
1031     DefaultParameterSetName="")]
1032
1033   param(
1034     [Parameter(Position=0,Mandatory=0)]
1035     [string]$buildFile = $script:psake.default_build_file_name,
1036     [Parameter(Position=1,Mandatory=0)]
1037     [string[]]$taskList = @(),
1038     [Parameter(Position=2,Mandatory=0)]
1039     [string]$framework = '3.5',
1040     [Parameter(Position=3,Mandatory=0)]
1041     [switch]$docs = $false,
1042     [Parameter(Position=4,Mandatory=0)]
1043     [System.Collections.Hashtable]$parameters = @{},
1044     [Parameter(Position=5, Mandatory=0)]
1045     [System.Collections.Hashtable]$properties = @{}
1046   )
1047
1048   Begin
1049   {
1050     $script:psake.build_success = $false
1051     $script:psake.framework_version = $framework
1052
1053     if ($script:context -eq $null)
1054     {
1055       $script:context = New-Object System.Collections.Stack
1056     }
1057
1058     $script:context.push(@{
1059                            "formatTaskNameString" = "Executing task: {0}";
1060                            "taskSetupScriptBlock" = $null;
1061                            "taskTearDownScriptBlock" = $null;
1062                            "executedTasks" = New-Object System.Collections.Stack;
1063                            "callStack" = New-Object System.Collections.Stack;
1064                            "originalEnvPath" = $env:path;
1065                            "originalDirectory" = Get-Location;
1066                            "originalErrorActionPreference" = $global:ErrorActionPreference;
1067                            "tasks" = @{};
1068                            "properties" = @();
1069                            "includes" = New-Object System.Collections.Queue;
1070     })
1071   }
1072
1073   Process
1074   {
1075     try
1076     {
1077     $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
1078
1079     <#
1080       If the default.ps1 file exists and the given "buildfile" isn't found assume that the given 
1081       $buildFile is actually the target Tasks to execute in the default.ps1 script.
1082     #>
1083     if((Test-Path $script:psake.default_build_file_name ) -and !(test-path $buildFile)) {
1084       $list = New-Object System.Collections.ArrayList
1085       foreach($t in $buildFile.Split(',')) {
1086         $t1 = $t.Trim()
1087         if($t1 -ne $null -or $t1 -ne "") {
1088           $list.Add($t1)
1089         }
1090       }
1091       $taskList = $list.ToArray()
1092       $buildFile = $script:psake.default_build_file_name
1093     }
1094
1095     # Execute the build file to set up the tasks and defaults
1096     Assert (test-path $buildFile) "Error: Could not find the build file, $buildFile."
1097
1098     $script:psake.build_script_file = dir $buildFile
1099     set-location $script:psake.build_script_file.Directory
1100     . $script:psake.build_script_file.FullName
1101
1102     if ($docs)
1103     {
1104     Write-Documentation
1105     Cleanup-Environment
1106     return
1107     }
1108
1109     Configure-BuildEnvironment
1110
1111     # N.B. The initial dot (.) indicates that variables initialized/modified
1112     #      in the propertyBlock are available in the parent scope.
1113     while ($script:context.Peek().includes.Count -gt 0)
1114     {
1115       $includeBlock = $script:context.Peek().includes.Dequeue()
1116       . $includeBlock
1117     }
1118
1119     foreach($key in $parameters.keys)
1120     {
1121       if (test-path "variable:\$key")
1122       {
1123         set-item -path "variable:\$key" -value $parameters.$key | out-null
1124       }
1125       else
1126       {
1127         new-item -path "variable:\$key" -value $parameters.$key | out-null
1128       }
1129     }
1130
1131     foreach($propertyBlock in $script:context.Peek().properties)
1132     {
1133       . $propertyBlock
1134     }
1135
1136     foreach($key in $properties.keys)
1137     {
1138       if (test-path "variable:\$key")
1139       {
1140         set-item -path "variable:\$key" -value $properties.$key | out-null
1141       }
1142     }
1143
1144     # Execute the list of tasks or the default task
1145     if($taskList.Length -ne 0)
1146     {
1147       foreach($task in $taskList)
1148       {
1149         ExecuteTask $task
1150       }
1151     }
1152     elseif ($script:context.Peek().tasks.default -ne $null)
1153     {
1154       ExecuteTask default
1155     }
1156     else
1157     {
1158       throw 'Error: default task required'
1159     }
1160
1161     $stopwatch.Stop()
1162
1163     "`nBuild Succeeded!`n"
1164
1165     Write-TaskTimeSummary
1166
1167     $script:psake.build_success = $true
1168     }
1169     catch
1170     {
1171     #Append detailed exception and script variables to error log file
1172     if ($script:psake.log_error)
1173     {
1174       $errorLogFile = "psake-error-log-{0}.log" -f ([DateTime]::Now.ToString("yyyyMMdd"))
1175       "-" * 70 >> $errorLogFile
1176       "{0}: An Error Occurred. See Error Details Below: " -f [DateTime]::Now >>$errorLogFile
1177       "-" * 70 >> $errorLogFile
1178       Resolve-Error $_ >> $errorLogFile
1179       "-" * 70 >> $errorLogFile
1180       "Script Variables" >> $errorLogFile
1181       "-" * 70 >> $errorLogFile
1182       Get-Variable -scope script >> $errorLogFile
1183     }
1184
1185     $buildFileName = Split-Path $buildFile -leaf
1186     if (test-path $buildFile) { $buildFileName = $script:psake.build_script_file.Name }
1187     Write-Host -foregroundcolor Red ($buildFileName + ":" + $_)
1188
1189     if ($script:psake.use_exit_on_error)
1190     {
1191       exit(1)
1192     }
1193     else
1194     {
1195       $script:psake.build_success = $false
1196     }
1197     }
1198   } #Process
1199
1200   End
1201   {
1202   # Clear out any global variables
1203   Cleanup-Environment
1204   [void]$script:context.Pop()
1205   }
1206 }
1207
1208 Export-ModuleMember -Function "Invoke-psake","Task","Properties","Include","FormatTaskName","TaskSetup","TaskTearDown","Assert","Exec"