diff --git a/Enyim.Caching.Log4NetAdapter/Enyim.Caching.Log4NetAdapter.csproj b/Enyim.Caching.Log4NetAdapter/Enyim.Caching.Log4NetAdapter.csproj index d8b535be..0da62292 100644 --- a/Enyim.Caching.Log4NetAdapter/Enyim.Caching.Log4NetAdapter.csproj +++ b/Enyim.Caching.Log4NetAdapter/Enyim.Caching.Log4NetAdapter.csproj @@ -15,8 +15,9 @@ - - ..\binaries\log4net\log4net.dll + + False + ..\packages\log4net.2.0.3\lib\net35-full\log4net.dll @@ -42,6 +43,7 @@ Designer + diff --git a/Enyim.Caching.Log4NetAdapter/packages.config b/Enyim.Caching.Log4NetAdapter/packages.config new file mode 100644 index 00000000..7eb67aa2 --- /dev/null +++ b/Enyim.Caching.Log4NetAdapter/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Enyim.Caching/Configuration/EndPointElementCollection.cs b/Enyim.Caching/Configuration/EndPointElementCollection.cs index 4e456a5d..3509e095 100755 --- a/Enyim.Caching/Configuration/EndPointElementCollection.cs +++ b/Enyim.Caching/Configuration/EndPointElementCollection.cs @@ -3,6 +3,7 @@ using System.Configuration; using System.Globalization; using System.Net; +using System.Net.Sockets; namespace Enyim.Caching.Configuration { @@ -41,18 +42,36 @@ public IList ToIPEndPointCollection() List retval = new List(this.Count); foreach (EndPointElement e in this) { - retval.Add(e.EndPoint); + try + { + retval.Add(e.EndPoint); + } + catch (SocketException) + { + if (false == SkipUnresolvedDns) + { + throw; + } + } } return retval.AsReadOnly(); } + + private const string SkipUnresolvedDnsKey = "skipUnresolvedDns"; + [ConfigurationProperty(SkipUnresolvedDnsKey, IsRequired = false, DefaultValue = false)] + public bool SkipUnresolvedDns + { + get { return (bool)base[SkipUnresolvedDnsKey]; } + set { base[SkipUnresolvedDnsKey] = value; } + } } } #region [ License information ] /* ************************************************************ * - * Copyright (c) 2010 Attila Kiskó, enyim.com + * Copyright (c) 2010 Attila Kiskó, enyim.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Enyim.Caching/Memcached/Locators/VBucketNodeLocator.cs b/Enyim.Caching/Memcached/Locators/VBucketNodeLocator.cs index 97f8d5d5..16573a15 100644 --- a/Enyim.Caching/Memcached/Locators/VBucketNodeLocator.cs +++ b/Enyim.Caching/Memcached/Locators/VBucketNodeLocator.cs @@ -40,8 +40,8 @@ public VBucketNodeLocator(string hashAlgorithm, VBucket[] buckets) throw new ArgumentException("Unknown hash algorithm: " + hashAlgorithm, "hashAlgorithm"); } - [ThreadStatic] - private static HashAlgorithm currentAlgo; + //[ThreadStatic] + //private static HashAlgorithm currentAlgo; //private HashAlgorithm GetAlgo() //{ diff --git a/Enyim.Caching/Memcached/Results/Helpers/ResultHelper.cs b/Enyim.Caching/Memcached/Results/Helpers/ResultHelper.cs index 2f6bfe7b..be0ba818 100644 --- a/Enyim.Caching/Memcached/Results/Helpers/ResultHelper.cs +++ b/Enyim.Caching/Memcached/Results/Helpers/ResultHelper.cs @@ -2,18 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Enyim.Caching.Memcached.Results.Factories; namespace Enyim.Caching.Memcached.Results.Helpers { - public static class ResultHelper { - public static string ProcessResponseData(ArraySegment data, string message = "") { - - if (data != null && data.Count > 0) + if (data.Count > 0) { try { diff --git a/Enyim.Caching/MemcachedClient.Results.cs b/Enyim.Caching/MemcachedClient.Results.cs index 3ca257cb..8d1b33ea 100644 --- a/Enyim.Caching/MemcachedClient.Results.cs +++ b/Enyim.Caching/MemcachedClient.Results.cs @@ -42,7 +42,7 @@ public IStoreOperationResult ExecuteStore(StoreMode mode, string key, object val ulong tmp = 0; int status; - return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor, null), ref tmp, out status); + return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor), ref tmp, out status); } /// @@ -58,7 +58,7 @@ public IStoreOperationResult ExecuteStore(StoreMode mode, string key, object val ulong tmp = 0; int status; - return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(null, expiresAt), ref tmp, out status); + return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(expiresAt), ref tmp, out status); } #endregion @@ -101,7 +101,7 @@ public IStoreOperationResult ExecuteCas(StoreMode mode, string key, object value /// A CasResult object containing the version of the item and the result of the operation (true if the item was successfully stored in the cache; false otherwise). public IStoreOperationResult ExecuteCas(StoreMode mode, string key, object value, TimeSpan validFor, ulong cas) { - return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor, null), cas); + return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor), cas); } /// @@ -115,7 +115,7 @@ public IStoreOperationResult ExecuteCas(StoreMode mode, string key, object value /// A CasResult object containing the version of the item and the result of the operation (true if the item was successfully stored in the cache; false otherwise). public IStoreOperationResult ExecuteCas(StoreMode mode, string key, object value, DateTime expiresAt, ulong cas) { - return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(null, expiresAt), cas); + return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(expiresAt), cas); } #endregion @@ -222,7 +222,7 @@ public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, ulong delta, TimeSpan validFor) { - return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null)); + return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor)); } /// @@ -236,7 +236,7 @@ public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, ulong delta, DateTime expiresAt) { - return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt)); + return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt)); } /// @@ -265,7 +265,7 @@ public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, ulong delta, TimeSpan validFor, ulong cas) { - return this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null), cas); + return this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor), cas); } /// @@ -280,7 +280,7 @@ public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteIncrement(string key, ulong defaultValue, ulong delta, DateTime expiresAt, ulong cas) { - return this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt), cas); + return this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt), cas); } /// @@ -307,7 +307,7 @@ public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, ulong delta, TimeSpan validFor) { - return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null)); + return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor)); } /// @@ -321,7 +321,7 @@ public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, ulong delta, DateTime expiresAt) { - return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt)); + return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt)); } /// @@ -350,7 +350,7 @@ public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, ulong delta, TimeSpan validFor, ulong cas) { - return this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null), cas); + return this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor), cas); } /// @@ -365,7 +365,7 @@ public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public IMutateOperationResult ExecuteDecrement(string key, ulong defaultValue, ulong delta, DateTime expiresAt, ulong cas) { - return this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt), cas); + return this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt), cas); } #endregion diff --git a/Enyim.Caching/MemcachedClient.cs b/Enyim.Caching/MemcachedClient.cs index 5041d4dc..b5319874 100755 --- a/Enyim.Caching/MemcachedClient.cs +++ b/Enyim.Caching/MemcachedClient.cs @@ -40,7 +40,8 @@ public partial class MemcachedClient : IMemcachedClient, IMemcachedResultsClient /// Initializes a new MemcachedClient instance using the default configuration section (enyim/memcached). /// public MemcachedClient() - : this(DefaultSettings) { } + : this(DefaultSettings) + { } protected IServerPool Pool { get { return this.pool; } } protected IMemcachedKeyTransformer KeyTransformer { get { return this.keyTransformer; } } @@ -53,7 +54,8 @@ public MemcachedClient() /// /// The name of the configuration section to be used for configuring the behavior of the client. public MemcachedClient(string sectionName) - : this(GetSection(sectionName)) { } + : this(GetSection(sectionName)) + { } /// /// Initializes a new instance of the using the specified configuration instance. @@ -80,7 +82,8 @@ public MemcachedClient(IMemcachedClientConfiguration configuration) } public MemcachedClient(IServerPool pool, IMemcachedKeyTransformer keyTransformer, ITranscoder transcoder) - : this(pool, keyTransformer, transcoder, null) { } + : this(pool, keyTransformer, transcoder, null) + { } public MemcachedClient(IServerPool pool, IMemcachedKeyTransformer keyTransformer, ITranscoder transcoder, IPerformanceMonitor performanceMonitor) { @@ -247,7 +250,7 @@ public bool Store(StoreMode mode, string key, object value, TimeSpan validFor) ulong tmp = 0; int status; - return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor, null), ref tmp, out status).Success; + return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor), ref tmp, out status).Success; } /// @@ -263,7 +266,7 @@ public bool Store(StoreMode mode, string key, object value, DateTime expiresAt) ulong tmp = 0; int status; - return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(null, expiresAt), ref tmp, out status).Success; + return this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(expiresAt), ref tmp, out status).Success; } /// @@ -292,7 +295,7 @@ public CasResult Cas(StoreMode mode, string key, object value, ulong cas) /// A CasResult object containing the version of the item and the result of the operation (true if the item was successfully stored in the cache; false otherwise). public CasResult Cas(StoreMode mode, string key, object value, TimeSpan validFor, ulong cas) { - var result = this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor, null), cas); + var result = this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(validFor), cas); return new CasResult { Cas = result.Cas, Result = result.Success, StatusCode = result.StatusCode.Value }; } @@ -307,7 +310,7 @@ public CasResult Cas(StoreMode mode, string key, object value, TimeSpan va /// A CasResult object containing the version of the item and the result of the operation (true if the item was successfully stored in the cache; false otherwise). public CasResult Cas(StoreMode mode, string key, object value, DateTime expiresAt, ulong cas) { - var result = this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(null, expiresAt), cas); + var result = this.PerformStore(mode, key, value, MemcachedClient.GetExpiration(expiresAt), cas); return new CasResult { Cas = result.Cas, Result = result.Success, StatusCode = result.StatusCode.Value }; } @@ -415,7 +418,7 @@ public ulong Increment(string key, ulong defaultValue, ulong delta) /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public ulong Increment(string key, ulong defaultValue, ulong delta, TimeSpan validFor) { - return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null)).Value; + return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor)).Value; } /// @@ -429,7 +432,7 @@ public ulong Increment(string key, ulong defaultValue, ulong delta, TimeSpan val /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public ulong Increment(string key, ulong defaultValue, ulong delta, DateTime expiresAt) { - return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt)).Value; + return this.PerformMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt)).Value; } /// @@ -459,7 +462,7 @@ public CasResult Increment(string key, ulong defaultValue, ulong delta, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public CasResult Increment(string key, ulong defaultValue, ulong delta, TimeSpan validFor, ulong cas) { - var result = this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null), cas); + var result = this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor), cas); return new CasResult { Cas = result.Cas, Result = result.Value, StatusCode = result.StatusCode.Value }; } @@ -475,7 +478,7 @@ public CasResult Increment(string key, ulong defaultValue, ulong delta, T /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public CasResult Increment(string key, ulong defaultValue, ulong delta, DateTime expiresAt, ulong cas) { - var result = this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt), cas); + var result = this.CasMutate(MutationMode.Increment, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt), cas); return new CasResult { Cas = result.Cas, Result = result.Value, StatusCode = result.StatusCode.Value }; } @@ -505,7 +508,7 @@ public ulong Decrement(string key, ulong defaultValue, ulong delta) /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public ulong Decrement(string key, ulong defaultValue, ulong delta, TimeSpan validFor) { - return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null)).Value; + return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor)).Value; } /// @@ -519,7 +522,7 @@ public ulong Decrement(string key, ulong defaultValue, ulong delta, TimeSpan val /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public ulong Decrement(string key, ulong defaultValue, ulong delta, DateTime expiresAt) { - return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt)).Value; + return this.PerformMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt)).Value; } /// @@ -549,7 +552,7 @@ public CasResult Decrement(string key, ulong defaultValue, ulong delta, u /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public CasResult Decrement(string key, ulong defaultValue, ulong delta, TimeSpan validFor, ulong cas) { - var result = this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor, null), cas); + var result = this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(validFor), cas); return new CasResult { Cas = result.Cas, Result = result.Value, StatusCode = result.StatusCode.Value }; } @@ -565,7 +568,7 @@ public CasResult Decrement(string key, ulong defaultValue, ulong delta, T /// If the client uses the Text protocol, the item must be inserted into the cache before it can be changed. It must be inserted as a . Moreover the Text protocol only works with values, so return value -1 always indicates that the item was not found. public CasResult Decrement(string key, ulong defaultValue, ulong delta, DateTime expiresAt, ulong cas) { - var result = this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(null, expiresAt), cas); + var result = this.CasMutate(MutationMode.Decrement, key, defaultValue, delta, MemcachedClient.GetExpiration(expiresAt), cas); return new CasResult { Cas = result.Cas, Result = result.Value, StatusCode = result.StatusCode.Value }; } @@ -935,28 +938,27 @@ private static void SafeWaitAllAndDispose(WaitHandle[] waitHandles) protected const int MaxSeconds = 60 * 60 * 24 * 30; protected static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1); - protected static uint GetExpiration(TimeSpan? validFor, DateTime? expiresAt) + protected static uint GetExpiration(TimeSpan validFor) { - if (validFor != null && expiresAt != null) - throw new ArgumentException("You cannot specify both validFor and expiresAt."); - // convert timespans to absolute dates - if (validFor != null) - { - // infinity - if (validFor == TimeSpan.Zero || validFor == TimeSpan.MaxValue) return 0; + // infinity + if (validFor == TimeSpan.Zero || validFor == TimeSpan.MaxValue) return 0; - expiresAt = DateTime.Now.Add(validFor.Value); - } + uint seconds = (uint)validFor.TotalSeconds; + if (seconds > MaxSeconds) + return GetExpiration(DateTime.Now.Add(validFor)); - DateTime dt = expiresAt.Value; + return seconds; + } - if (dt < UnixEpoch) throw new ArgumentOutOfRangeException("expiresAt", "expiresAt must be >= 1970/1/1"); + protected static uint GetExpiration(DateTime expiresAt) + { + if (expiresAt < UnixEpoch) throw new ArgumentOutOfRangeException("expiresAt", "expiresAt must be >= 1970/1/1"); // accept MaxValue as infinite - if (dt == DateTime.MaxValue) return 0; + if (expiresAt == DateTime.MaxValue) return 0; - uint retval = (uint)(dt.ToUniversalTime() - UnixEpoch).TotalSeconds; + uint retval = (uint)(expiresAt.ToUniversalTime() - UnixEpoch).TotalSeconds; return retval; } diff --git a/build/psake-config.ps1 b/build/psake-config.ps1 new file mode 100644 index 00000000..54587016 --- /dev/null +++ b/build/psake-config.ps1 @@ -0,0 +1,21 @@ +<# +------------------------------------------------------------------- +Defaults +------------------------------------------------------------------- +$config.buildFileName="default.ps1" +$config.framework = "4.0" +$config.taskNameFormat="Executing {0}" +$config.verboseError=$false +$config.coloredOutput = $true +$config.modules=$null + +------------------------------------------------------------------- +Load modules from .\modules folder and from file my_module.psm1 +------------------------------------------------------------------- +$config.modules=(".\modules\*.psm1",".\my_module.psm1") + +------------------------------------------------------------------- +Use scriptblock for taskNameFormat +------------------------------------------------------------------- +$config.taskNameFormat= { param($taskName) "Executing $taskName at $(get-date)" } +#> diff --git a/build/psake.cmd b/build/psake.cmd new file mode 100644 index 00000000..f1aadc48 --- /dev/null +++ b/build/psake.cmd @@ -0,0 +1,14 @@ +@echo off +rem Helper script for those who want to run psake from cmd.exe +rem Example run from cmd.exe: +rem psake "default.ps1" "BuildHelloWord" "4.0" + +if '%1'=='/?' goto help +if '%1'=='-help' goto help +if '%1'=='-h' goto help + +powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%~dp0\psake.ps1' %*; if ($psake.build_success -eq $false) { exit 1 } else { exit 0 }" +exit /B %errorlevel% + +:help +powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%~dp0\psake.ps1' -help" diff --git a/build/psake.ps1 b/build/psake.ps1 new file mode 100644 index 00000000..23c6f2d8 --- /dev/null +++ b/build/psake.ps1 @@ -0,0 +1,53 @@ +# Helper script for those who want to run psake without importing the module. +# Example run from PowerShell: +# .\psake.ps1 "default.ps1" "BuildHelloWord" "4.0" + +# Must match parameter definitions for psake.psm1/invoke-psake +# otherwise named parameter binding fails +param( + [Parameter(Position=0,Mandatory=0)] + [string]$buildFile, + [Parameter(Position=1,Mandatory=0)] + [string[]]$taskList = @(), + [Parameter(Position=2,Mandatory=0)] + [string]$framework, + [Parameter(Position=3,Mandatory=0)] + [switch]$docs = $false, + [Parameter(Position=4,Mandatory=0)] + [System.Collections.Hashtable]$parameters = @{}, + [Parameter(Position=5, Mandatory=0)] + [System.Collections.Hashtable]$properties = @{}, + [Parameter(Position=6, Mandatory=0)] + [alias("init")] + [scriptblock]$initialization = {}, + [Parameter(Position=7, Mandatory=0)] + [switch]$nologo = $false, + [Parameter(Position=8, Mandatory=0)] + [switch]$help = $false, + [Parameter(Position=9, Mandatory=0)] + [string]$scriptPath, + [Parameter(Position=10,Mandatory=0)] + [switch]$detailedDocs = $false +) + +# setting $scriptPath here, not as default argument, to support calling as "powershell -File psake.ps1" +if (!$scriptPath) { + $scriptPath = $(Split-Path -parent $MyInvocation.MyCommand.path) +} + +# '[p]sake' is the same as 'psake' but $Error is not polluted +remove-module [p]sake +import-module (join-path $scriptPath psake.psm1) +if ($help) { + Get-Help Invoke-psake -full + return +} + +if ($buildFile -and (-not(test-path $buildFile))) { + $absoluteBuildFile = (join-path $scriptPath $buildFile) + if (test-path $absoluteBuildFile) { + $buildFile = $absoluteBuildFile + } +} + +Invoke-psake $buildFile $taskList $framework $docs $parameters $properties $initialization $nologo $detailedDocs diff --git a/build/psake.psm1 b/build/psake.psm1 index 46410286..948cb8f1 100644 --- a/build/psake.psm1 +++ b/build/psake.psm1 @@ -1,5 +1,5 @@ # psake -# Copyright (c) 2010 James Kovacs +# Copyright (c) 2012 James Kovacs # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -20,1189 +20,841 @@ #Requires -Version 2.0 -#-- Private Module Variables (Listed here for quick reference) -[system.collections.stack]$script:context +#-- Public Module Functions --# -#-- Public Module Variables -- The psake hashtable variable is initialized in the invoke-psake function -$script:psake = @{} -$script:psake.use_exit_on_error = $false # determines if psake uses the "exit()" function when an exception occurs -$script:psake.log_error = $false # determines if the exception details are written to a file -$script:psake.build_success = $false # indicates that the current build was successful -$script:psake.version = "4.00" # contains the current version of psake -$script:psake.build_script_file = $null # contains a System.IO.FileInfo for the current build file -$script:psake.framework_version = "" # contains the framework version # for the current build -$script:psake.default_build_file_name = 'default.ps1' - -Export-ModuleMember -Variable "psake" - -#-- Private Module Functions -function ExecuteTask +# .ExternalHelp psake.psm1-help.xml +function Invoke-Task { - param([string]$taskName) - - Assert (![string]::IsNullOrEmpty($taskName)) "Task name should not be null or empty string" - - $taskKey = $taskName.ToLower() - - Assert ($script:context.Peek().tasks.Contains($taskKey)) "task [$taskName] does not exist" - - if ($script:context.Peek().executedTasks.Contains($taskKey)) - { - return - } - - Assert (!$script:context.Peek().callStack.Contains($taskKey)) "Error: Circular reference found for task, $taskName" - - $script:context.Peek().callStack.Push($taskKey) - - $task = $script:context.Peek().tasks.$taskKey - - $taskName = $task.Name + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)] [string]$taskName + ) - $precondition_is_valid = if ($task.Precondition -ne $null) {& $task.Precondition} else {$true} + Assert $taskName ($msgs.error_invalid_task_name) - if (!$precondition_is_valid) - { - "Precondition was false not executing $name" - } - else - { - if ($taskKey -ne 'default') - { - $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + $taskKey = $taskName.ToLower() - if ( ($task.PreAction -ne $null) -or ($task.PostAction -ne $null) ) - { - Assert ($task.Action -ne $null) "Error: Action parameter must be specified when using PreAction or PostAction parameters" - } + if ($currentContext.aliases.Contains($taskKey)) { + $taskName = $currentContext.aliases.$taskKey.Name + $taskKey = $taskName.ToLower() + } - if ($task.Action -ne $null) - { - try - { - foreach($childTask in $task.DependsOn) - { - ExecuteTask $childTask - } - - $script:context.Peek().currentTaskName = $taskName - - if ($script:context.Peek().taskSetupScriptBlock -ne $null) - { - & $script:context.Peek().taskSetupScriptBlock - } - - if ($task.PreAction -ne $null) - { - & $task.PreAction - } - - $script:context.Peek().formatTaskNameString -f $taskName - & $task.Action - - if ($task.PostAction -ne $null) - { - & $task.PostAction - } - - if ($script:context.Peek().taskTearDownScriptBlock -ne $null) - { - & $script:context.Peek().taskTearDownScriptBlock - } + $currentContext = $psake.context.Peek() + + Assert ($currentContext.tasks.Contains($taskKey)) ($msgs.error_task_name_does_not_exist -f $taskName) + + if ($currentContext.executedTasks.Contains($taskKey)) { return } + + Assert (!$currentContext.callStack.Contains($taskKey)) ($msgs.error_circular_reference -f $taskName) + + $currentContext.callStack.Push($taskKey) + + $task = $currentContext.tasks.$taskKey + + $precondition_is_valid = & $task.Precondition + + if (!$precondition_is_valid) { + WriteColoredOutput ($msgs.precondition_was_false -f $taskName) -foregroundcolor Cyan + } else { + if ($taskKey -ne 'default') { + + if ($task.PreAction -or $task.PostAction) { + Assert ($task.Action -ne $null) ($msgs.error_missing_action_parameter -f $taskName) + } + + if ($task.Action) { + try { + foreach($childTask in $task.DependsOn) { + Invoke-Task $childTask + } + + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + $currentContext.currentTaskName = $taskName + + & $currentContext.taskSetupScriptBlock + + if ($task.PreAction) { + & $task.PreAction + } + + if ($currentContext.config.taskNameFormat -is [ScriptBlock]) { + & $currentContext.config.taskNameFormat $taskName + } else { + WriteColoredOutput ($currentContext.config.taskNameFormat -f $taskName) -foregroundcolor Cyan + } + + foreach ($variable in $task.requiredVariables) { + Assert ((test-path "variable:$variable") -and ((get-variable $variable).Value -ne $null)) ($msgs.required_variable_not_set -f $variable, $taskName) + } + + & $task.Action + + if ($task.PostAction) { + & $task.PostAction + } + + & $currentContext.taskTearDownScriptBlock + $task.Duration = $stopwatch.Elapsed + } catch { + if ($task.ContinueOnError) { + "-"*70 + WriteColoredOutput ($msgs.continue_on_error -f $taskName,$_) -foregroundcolor Yellow + "-"*70 + $task.Duration = $stopwatch.Elapsed + } else { + throw $_ + } + } + } else { + # no action was specified but we still execute all the dependencies + foreach($childTask in $task.DependsOn) { + Invoke-Task $childTask + } + } + } else { + foreach($childTask in $task.DependsOn) { + Invoke-Task $childTask + } } - catch - { - if ($task.ContinueOnError) - { - "-"*70 - "Error in Task [$taskName] $_" - "-"*70 - } - else - { - throw $_ - } - } - } # if ($task.Action -ne $null) - else - { - #no Action was specified but we still execute all the dependencies - foreach($childTask in $task.DependsOn) - { - ExecuteTask $childTask - } - } - $stopwatch.stop() - $task.Duration = $stopwatch.Elapsed - } # if ($name.ToLower() -ne 'default') - else - { - foreach($childTask in $task.DependsOn) - { - ExecuteTask $childTask - } - } - if ($task.Postcondition -ne $null) - { - Assert (& $task.Postcondition) "Error: Postcondition failed for $taskName" + Assert (& $task.Postcondition) ($msgs.postcondition_failed -f $taskName) } - } - - $poppedTaskKey = $script:context.Peek().callStack.Pop() - Assert ($poppedTaskKey -eq $taskKey) "Error: CallStack was corrupt. Expected $taskKey, but got $poppedTaskKey." + $poppedTaskKey = $currentContext.callStack.Pop() + Assert ($poppedTaskKey -eq $taskKey) ($msgs.error_corrupt_callstack -f $taskKey,$poppedTaskKey) - $script:context.Peek().executedTasks.Push($taskKey) + $currentContext.executedTasks.Push($taskKey) } -function Configure-BuildEnvironment +# .ExternalHelp psake.psm1-help.xml +function Exec { - if ($framework.Length -ne 3 -and $framework.Length -ne 6) { - throw "Error: Invalid .NET Framework version, $framework, specified" - } - $versionPart = $framework.Substring(0,3) - $bitnessPart = $framework.Substring(3) - $versions = $null - switch ($versionPart) - { - '1.0' { $versions = @('v1.0.3705') } - '1.1' { $versions = @('v1.1.4322') } - '2.0' { $versions = @('v2.0.50727') } - '3.0' { $versions = @('v2.0.50727') } - '3.5' { $versions = @('v3.5','v2.0.50727') } - '4.0' { $versions = @('v4.0.30319') } - default { throw "Error: Unknown .NET Framework version, $versionPart, specified in $framework" } - } - - $bitness = 'Framework' - if($versionPart -ne '1.0' -and $versionPart -ne '1.1') { - switch ($bitnessPart) - { - 'x86' { $bitness = 'Framework' } - 'x64' { $bitness = 'Framework64' } - $null { - $ptrSize = [System.IntPtr]::Size - switch ($ptrSize) - { - 4 { $bitness = 'Framework' } - 8 { $bitness = 'Framework64' } - default { throw "Error: Unknown pointer size ($ptrSize) returned from System.IntPtr." } - } - } - default { throw "Error: Unknown .NET Framework bitness, $bitnessPart, specified in $framework" } - } - } - $frameworkDirs = $versions | foreach { "$env:windir\Microsoft.NET\$bitness\$_\" } + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, + [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd), + [Parameter(Position=2,Mandatory=0)][int]$maxRetries = 0, + [Parameter(Position=3,Mandatory=0)][string]$retryTriggerErrorPattern = $null + ) - $frameworkDirs | foreach { Assert (test-path $_) "Error: No .NET Framework installation directory found at $_" } + $tryCount = 1 - $env:path = [string]::Join(';', $frameworkDirs) + ";$env:path" - #if any error occurs in a PS function then "stop" processing immediately - # this does not effect any external programs that return a non-zero exit code - $global:ErrorActionPreference = "Stop" -} + do { + try { + $global:lastexitcode = 0 + & $cmd + if ($lastexitcode -ne 0) { + throw ("Exec: " + $errorMessage) + } + break + } + catch [Exception] + { + if ($tryCount -gt $maxRetries) { + throw $_ + } -function Cleanup-Environment -{ - $env:path = $script:context.Peek().originalEnvPath - Set-Location $script:context.Peek().originalDirectory - $global:ErrorActionPreference = $script:context.Peek().originalErrorActionPreference -} + if ($retryTriggerErrorPattern -ne $null) { + $isMatch = [regex]::IsMatch($_.Exception.Message, $retryTriggerErrorPattern) -#borrowed from Jeffrey Snover http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx -function Resolve-Error($ErrorRecord=$Error[0]) -{ - "ErrorRecord" - $ErrorRecord | Format-List * -Force | Out-String -Stream | ? {$_} - "" - "ErrorRecord.InvocationInfo" - $ErrorRecord.InvocationInfo | Format-List * | Out-String -Stream | ? {$_} - "" - "Exception" - $Exception = $ErrorRecord.Exception - for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException)) - { - "$i" * 70 - $Exception | Format-List * -Force | Out-String -Stream | ? {$_} - "" - } -} + if ($isMatch -eq $false) { + throw $_ + } + } -function Write-Documentation -{ - $list = New-Object System.Collections.ArrayList - foreach($key in $script:context.Peek().tasks.Keys) - { - if($key -eq "default") - { - continue - } - $task = $script:context.Peek().tasks.$key - $content = "" | Select-Object Name, Description - $content.Name = $task.Name - $content.Description = $task.Description - $index = $list.Add($content) - } - - $list | Sort 'Name' | Format-Table -Auto -} + Write-Host "Try $tryCount failed, retrying again in 1 second..." -function Write-TaskTimeSummary -{ - "-"*70 - "Build Time Report" - "-"*70 - $list = @() - while ($script:context.Peek().executedTasks.Count -gt 0) - { - $taskKey = $script:context.Peek().executedTasks.Pop() - $task = $script:context.Peek().tasks.$taskKey - if($taskKey -eq "default") - { - continue - } - $list += "" | Select-Object @{Name="Name";Expression={$task.Name}}, @{Name="Duration";Expression={$task.Duration}} - } - [Array]::Reverse($list) - $list += "" | Select-Object @{Name="Name";Expression={"Total:"}}, @{Name="Duration";Expression={$stopwatch.Elapsed}} - $list | Format-Table -Auto | Out-String -Stream | ? {$_} # using "Out-String -Stream" to filter out the blank line that Format-Table prepends -} + $tryCount++ -#-- Public Module Functions -function Exec -{ -<# -.SYNOPSIS -Helper function for executing command-line programs. - -.DESCRIPTION -This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode to see if an error occcured. -If an error is detected then an exception is thrown. This function allows you to run command-line programs without -having to explicitly check fthe $lastexitcode variable. - -.PARAMETER cmd -The scriptblock to execute. This scriptblock will typically contain the command-line invocation. -Required - -.PARAMETER errorMessage -The error message used for the exception that is thrown. -Optional - -.EXAMPLE -exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" - -This example calls the svn command-line client. - -.LINK -Assert -Invoke-psake -Task -Properties -Include -FormatTaskName -TaskSetup -TaskTearDown -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - - param( - [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, - [Parameter(Position=1,Mandatory=0)][string]$errorMessage = "Error executing command: " + $cmd - ) - & $cmd - if ($lastexitcode -ne 0) - { - throw $errorMessage - } + [System.Threading.Thread]::Sleep([System.TimeSpan]::FromSeconds(1)) + } + } + while ($true) } +# .ExternalHelp psake.psm1-help.xml function Assert { -<# -.SYNOPSIS -Helper function for "Design by Contract" assertion checking. - -.DESCRIPTION -This is a helper function that makes the code less noisy by eliminating many of the "if" statements -that are normally required to verify assumptions in the code. - -.PARAMETER conditionToCheck -The boolean condition to evaluate -Required - -.PARAMETER failureMessage -The error message used for the exception if the conditionToCheck parameter is false -Required - -.EXAMPLE -Assert $false "This always throws an exception" - -This example always throws an exception - -.EXAMPLE -Assert ( ($i % 2) -eq 0 ) "%i is not an even number" - -This exmaple may throw an exception if $i is not an even number - -.LINK -Invoke-psake -Task -Properties -Include -FormatTaskName -TaskSetup -TaskTearDown - -.NOTES -It might be necessary to wrap the condition with paranthesis to force PS to evaluate the condition -so that a boolean value is calculated and passed into the 'conditionToCheck' parameter. - -Example: - Assert 1 -eq 2 "1 doesn't equal 2" - -PS will pass 1 into the condtionToCheck variable and PS will look for a parameter called "eq" and -throw an exception with the following message "A parameter cannot be found that matches parameter name 'eq'" - -The solution is to wrap the condition in () so that PS will evaluate it first. - - Assert (1 -eq 2) "1 doesn't equal 2" -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - - param( - [Parameter(Position=0,Mandatory=1)]$conditionToCheck, - [Parameter(Position=1,Mandatory=1)]$failureMessage - ) - if (!$conditionToCheck) { throw $failureMessage } + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)]$conditionToCheck, + [Parameter(Position=1,Mandatory=1)]$failureMessage + ) + if (!$conditionToCheck) { + throw ("Assert: " + $failureMessage) + } } +# .ExternalHelp psake.psm1-help.xml function Task { -<# -.SYNOPSIS -Defines a build task to be executed by psake - -.DESCRIPTION -This function creates a 'task' object that will be used by the psake engine to execute a build task. -Note: There must be at least one task called 'default' in the build script - -.PARAMETER Name -The name of the task -Required - -.PARAMETER Action -A scriptblock containing the statements to execute -Optional - -.PARAMETER PreAction -A scriptblock to be executed before the 'Action' scriptblock. -Note: This parameter is ignored if the 'Action' scriptblock is not defined. -Optional - -.PARAMETER PostAction -A scriptblock to be executed after the 'Action' scriptblock. -Note: This parameter is ignored if the 'Action' scriptblock is not defined. -Optional - -.PARAMETER Precondition -A scriptblock that is executed to determine if the task is executed or skipped. -This scriptblock should return $true or $false -Optional - -.PARAMETER Postcondition -A scriptblock that is executed to determine if the task completed its job correctly. -An exception is thrown if the scriptblock returns $false. -Optional - -.PARAMETER ContinueOnError -If this switch parameter is set then the task will not cause the build to fail when an exception is thrown - -.PARAMETER Depends -An array of tasks that this task depends on. They will be executed before the current task is executed. - -.PARAMETER Description -A description of the task. - -.EXAMPLE -A sample build script is shown below: - -task default -depends Test + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][string]$name = $null, + [Parameter(Position=1,Mandatory=0)][scriptblock]$action = $null, + [Parameter(Position=2,Mandatory=0)][scriptblock]$preaction = $null, + [Parameter(Position=3,Mandatory=0)][scriptblock]$postaction = $null, + [Parameter(Position=4,Mandatory=0)][scriptblock]$precondition = {$true}, + [Parameter(Position=5,Mandatory=0)][scriptblock]$postcondition = {$true}, + [Parameter(Position=6,Mandatory=0)][switch]$continueOnError = $false, + [Parameter(Position=7,Mandatory=0)][string[]]$depends = @(), + [Parameter(Position=8,Mandatory=0)][string[]]$requiredVariables = @(), + [Parameter(Position=9,Mandatory=0)][string]$description = $null, + [Parameter(Position=10,Mandatory=0)][string]$alias = $null, + [Parameter(Position=11,Mandatory=0)][string]$maxRetries = 0, + [Parameter(Position=12,Mandatory=0)][string]$retryTriggerErrorPattern = $null + ) + if ($name -eq 'default') { + Assert (!$action) ($msgs.error_default_task_cannot_have_action) + } -task Test -depends Compile, Clean { - "This is a test" -} + $newTask = @{ + Name = $name + DependsOn = $depends + PreAction = $preaction + Action = $action + PostAction = $postaction + Precondition = $precondition + Postcondition = $postcondition + ContinueOnError = $continueOnError + Description = $description + Duration = [System.TimeSpan]::Zero + RequiredVariables = $requiredVariables + Alias = $alias + MaxRetries = $maxRetries + RetryTriggerErrorPattern = $retryTriggerErrorPattern + } -task Compile -depends Clean { - "Compile" -} + $taskKey = $name.ToLower() -task Clean { - "Clean" -} + $currentContext = $psake.context.Peek() -The 'default' task is required and should not contain an 'Action' parameter. -It uses the 'depends' parameter to specify that 'Test' is a dependency + Assert (!$currentContext.tasks.ContainsKey($taskKey)) ($msgs.error_duplicate_task_name -f $name) -The 'Test' task uses the 'depends' parameter to specify that 'Compile' and 'Clean' are dependencies -The 'Compile' task depends on the 'Clean' task. + $currentContext.tasks.$taskKey = $newTask -Note: -The 'Action' parameter is defaulted to the script block following the 'Clean' task. + if($alias) + { + $aliasKey = $alias.ToLower() -The equivalent 'Test' task is shown below: + Assert (!$currentContext.aliases.ContainsKey($aliasKey)) ($msgs.error_duplicate_alias_name -f $alias) -task Test -depends Compile, Clean -Action { - $testMessage + $currentContext.aliases.$aliasKey = $newTask + } } -The output for the above sample build script is shown below: -Executing task, Clean... -Clean -Executing task, Compile... -Compile -Executing task, Test... -This is a test - -Build Succeeded! - ----------------------------------------------------------------------- -Build Time Report ----------------------------------------------------------------------- -Name Duration ----- -------- -Clean 00:00:00.0065614 -Compile 00:00:00.0133268 -Test 00:00:00.0225964 -Total: 00:00:00.0782496 - -.LINK -Invoke-psake -Properties -Include -FormatTaskName -TaskSetup -TaskTearDown -Assert -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - param( - [Parameter(Position=0,Mandatory=1)] - [string]$name = $null, - [Parameter(Position=1,Mandatory=0)] - [scriptblock]$action = $null, - [Parameter(Position=2,Mandatory=0)] - [scriptblock]$preaction = $null, - [Parameter(Position=3,Mandatory=0)] - [scriptblock]$postaction = $null, - [Parameter(Position=4,Mandatory=0)] - [scriptblock]$precondition = $null, - [Parameter(Position=5,Mandatory=0)] - [scriptblock]$postcondition = $null, - [Parameter(Position=6,Mandatory=0)] - [switch]$continueOnError = $false, - [Parameter(Position=7,Mandatory=0)] - [string[]]$depends = @(), - [Parameter(Position=8,Mandatory=0)] - [string]$description = $null +# .ExternalHelp psake.psm1-help.xml +function Properties { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$properties ) - - if ($name.ToLower() -eq 'default') - { - Assert ($action -eq $null) "Error: 'default' task cannot specify an action" - } - - $newTask = @{ - Name = $name - DependsOn = $depends - PreAction = $preaction - Action = $action - PostAction = $postaction - Precondition = $precondition - Postcondition = $postcondition - ContinueOnError = $continueOnError - Description = $description - Duration = 0 - } - - $taskKey = $name.ToLower() - - Assert (!$script:context.Peek().tasks.ContainsKey($taskKey)) "Error: Task, $name, has already been defined." - - $script:context.Peek().tasks.$taskKey = $newTask + $psake.context.Peek().properties += $properties } -function Properties -{ -<# -.SYNOPSIS -Define a scriptblock that contains assignments to variables that will be available to all tasks in the build script - -.DESCRIPTION -A build script may declare a "Properies" function which allows you to define -variables that will be available to all the "Task" functions in the build script. - -.PARAMETER properties -The script block containing all the variable assignment statements -Required - -.EXAMPLE -A sample build script is shown below: - -Properties { - $build_dir = "c:\build" - $connection_string = "datasource=localhost;initial catalog=northwind;integrated security=sspi" -} - -Task default -depends Test - -Task Test -depends Compile, Clean { +# .ExternalHelp psake.psm1-help.xml +function Include { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][string]$fileNamePathToInclude + ) + Assert (test-path $fileNamePathToInclude -pathType Leaf) ($msgs.error_invalid_include_path -f $fileNamePathToInclude) + $psake.context.Peek().includes.Enqueue((Resolve-Path $fileNamePathToInclude)); } -Task Compile -depends Clean { +# .ExternalHelp psake.psm1-help.xml +function FormatTaskName { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)]$format + ) + $psake.context.Peek().config.taskNameFormat = $format } -Task Clean { +# .ExternalHelp psake.psm1-help.xml +function TaskSetup { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$setup + ) + $psake.context.Peek().taskSetupScriptBlock = $setup } -.LINK -Invoke-psake -Task -Include -FormatTaskName -TaskSetup -TaskTearDown -Assert - -.NOTES -You can have more than 1 "Properties" function defined in the script -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - param( - [Parameter(Position=0,Mandatory=1)] - [scriptblock]$properties - ) - $script:context.Peek().properties += $properties +# .ExternalHelp psake.psm1-help.xml +function TaskTearDown { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$teardown + ) + $psake.context.Peek().taskTearDownScriptBlock = $teardown } -function Include -{ -<# -.SYNOPSIS -Include the functions or code of another powershell script file into the current build script's scope - -.DESCRIPTION -A build script may declare an "includes" function which allows you to define -a file containing powershell code to be included and added to the scope of -the currently running build script. - -.PARAMETER fileNamePathToInclude -A string containing the path and name of the powershell file to include -Required - -.EXAMPLE -A sample build script is shown below: - -Include ".\build_utils.ps1" - -Task default -depends Test +# .ExternalHelp psake.psm1-help.xml +function Framework { + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][string]$framework + ) + $psake.context.Peek().config.framework = $framework + ConfigureBuildEnvironment +} + +# .ExternalHelp psake.psm1-help.xml +function Invoke-psake { + [CmdletBinding()] + param( + [Parameter(Position = 0, Mandatory = 0)][string] $buildFile, + [Parameter(Position = 1, Mandatory = 0)][string[]] $taskList = @(), + [Parameter(Position = 2, Mandatory = 0)][string] $framework, + [Parameter(Position = 3, Mandatory = 0)][switch] $docs = $false, + [Parameter(Position = 4, Mandatory = 0)][hashtable] $parameters = @{}, + [Parameter(Position = 5, Mandatory = 0)][hashtable] $properties = @{}, + [Parameter(Position = 6, Mandatory = 0)][alias("init")][scriptblock] $initialization = {}, + [Parameter(Position = 7, Mandatory = 0)][switch] $nologo = $false, + [Parameter(Position = 8, Mandatory = 0)][switch] $detailedDocs = $false + ) + try { + if (-not $nologo) { + "psake version {0}`nCopyright (c) 2010-2014 James Kovacs & Contributors`n" -f $psake.version + } -Task Test -depends Compile, Clean { -} + if (!$buildFile) { + $buildFile = $psake.config_default.buildFileName + } + elseif (!(test-path $buildFile -pathType Leaf) -and (test-path $psake.config_default.buildFileName -pathType Leaf)) { + # If the $config.buildFileName file exists and the given "buildfile" isn 't found assume that the given + # $buildFile is actually the target Tasks to execute in the $config.buildFileName script. + $taskList = $buildFile.Split(', ') + $buildFile = $psake.config_default.buildFileName + } -Task Compile -depends Clean { -} + # Execute the build file to set up the tasks and defaults + Assert (test-path $buildFile -pathType Leaf) ($msgs.error_build_file_not_found -f $buildFile) -Task Clean { -} + $psake.build_script_file = get-item $buildFile + $psake.build_script_dir = $psake.build_script_file.DirectoryName + $psake.build_success = $false + $psake.context.push(@{ + "taskSetupScriptBlock" = {}; + "taskTearDownScriptBlock" = {}; + "executedTasks" = new-object System.Collections.Stack; + "callStack" = new-object System.Collections.Stack; + "originalEnvPath" = $env:path; + "originalDirectory" = get-location; + "originalErrorActionPreference" = $global:ErrorActionPreference; + "tasks" = @{}; + "aliases" = @{}; + "properties" = @(); + "includes" = new-object System.Collections.Queue; + "config" = CreateConfigurationForNewContext $buildFile $framework + }) -.LINK -Invoke-psake -Task -Properties -FormatTaskName -TaskSetup -TaskTearDown -Assert - -.NOTES -You can have more than 1 "Include" function defined in the script -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - param( - [Parameter(Position=0,Mandatory=1)] - [string]$fileNamePathToInclude - ) - Assert (test-path $fileNamePathToInclude) "Error: Unable to include $fileNamePathToInclude. File not found." - $script:context.Peek().includes.Enqueue((Resolve-Path $fileNamePathToInclude)); -} + LoadConfiguration $psake.build_script_dir -function FormatTaskName -{ -<# -.SYNOPSIS -Allows you to define a format mask that will be used when psake displays -the task name + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() -.DESCRIPTION -Allows you to define a format mask that will be used when psake displays -the task name. The default is "Executing task, {0}..." + set-location $psake.build_script_dir -.PARAMETER format -A string containing the format mask to use, it should contain a placeholder ({0}) -that will be used to substitute the task name. -Required + LoadModules -.EXAMPLE -A sample build script is shown below: + $frameworkOldValue = $framework + . $psake.build_script_file.FullName -FormatTaskName "[Task: {0}]" + $currentContext = $psake.context.Peek() -Task default -depends Test + if ($framework -ne $frameworkOldValue) { + writecoloredoutput $msgs.warning_deprecated_framework_variable -foregroundcolor Yellow + $currentContext.config.framework = $framework + } -Task Test -depends Compile, Clean { -} + ConfigureBuildEnvironment -Task Compile -depends Clean { -} + while ($currentContext.includes.Count -gt 0) { + $includeFilename = $currentContext.includes.Dequeue() + . $includeFilename + } -Task Clean { -} + if ($docs -or $detailedDocs) { + WriteDocumentation($detailedDocs) + CleanupEnvironment + return + } -You should get the following output: ------------------------------------- - -[Task: Clean] -[Task: Compile] -[Task: Test] - -Build Succeeded - ----------------------------------------------------------------------- -Build Time Report ----------------------------------------------------------------------- -Name Duration ----- -------- -Clean 00:00:00.0043477 -Compile 00:00:00.0102130 -Test 00:00:00.0182858 -Total: 00:00:00.0698071 - -.LINK -Invoke-psake -Include -Task -Properties -TaskSetup -TaskTearDown -Assert -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - param( - [Parameter(Position=0,Mandatory=1)] - [string]$format - ) - $script:context.Peek().formatTaskNameString = $format -} + foreach ($key in $parameters.keys) { + if (test-path "variable:\$key") { + set-item -path "variable:\$key" -value $parameters.$key -WhatIf:$false -Confirm:$false | out-null + } else { + new-item -path "variable:\$key" -value $parameters.$key -WhatIf:$false -Confirm:$false | out-null + } + } -function TaskSetup -{ -<# -.SYNOPSIS -Adds a scriptblock that will be executed before each task + # The initial dot (.) indicates that variables initialized/modified in the propertyBlock are available in the parent scope. + foreach ($propertyBlock in $currentContext.properties) { + . $propertyBlock + } -.DESCRIPTION -This function will accept a scriptblock that will be executed before each -task in the build script. + foreach ($key in $properties.keys) { + if (test-path "variable:\$key") { + set-item -path "variable:\$key" -value $properties.$key -WhatIf:$false -Confirm:$false | out-null + } + } -.PARAMETER include -A scriptblock to execute -Required + # Simple dot sourcing will not work. We have to force the script block into our + # module's scope in order to initialize variables properly. + . $MyInvocation.MyCommand.Module $initialization + + # Execute the list of tasks or the default task + if ($taskList) { + foreach ($task in $taskList) { + invoke-task $task + } + } elseif ($currentContext.tasks.default) { + invoke-task default + } else { + throw $msgs.error_no_default_task + } -.EXAMPLE -A sample build script is shown below: + WriteColoredOutput ("`n" + $msgs.build_success + "`n") -foregroundcolor Green + + WriteTaskTimeSummary $stopwatch.Elapsed + + $psake.build_success = $true + } catch { + $currentConfig = GetCurrentConfigurationOrDefault + if ($currentConfig.verboseError) { + $error_message = "{0}: An Error Occurred. See Error Details Below: `n" -f (Get-Date) + $error_message += ("-" * 70) + "`n" + $error_message += "Error: {0}`n" -f (ResolveError $_ -Short) + $error_message += ("-" * 70) + "`n" + $error_message += ResolveError $_ + $error_message += ("-" * 70) + "`n" + $error_message += "Script Variables" + "`n" + $error_message += ("-" * 70) + "`n" + $error_message += get-variable -scope script | format-table | out-string + } else { + # ($_ | Out-String) gets error messages with source information included. + $error_message = "Error: {0}: `n{1}" -f (Get-Date), (ResolveError $_ -Short) + } -Task default -depends Test + $psake.build_success = $false -Task Test -depends Compile, Clean { + # if we are running in a nested scope (i.e. running a psake script from a psake script) then we need to re-throw the exception + # so that the parent script will fail otherwise the parent script will report a successful build + $inNestedScope = ($psake.context.count -gt 1) + if ( $inNestedScope ) { + throw $_ + } else { + if (!$psake.run_by_psake_build_tester) { + WriteColoredOutput $error_message -foregroundcolor Red + } + } + } finally { + CleanupEnvironment + } } -Task Compile -depends Clean { -} +#-- Private Module Functions --# +function WriteColoredOutput { + param( + [string] $message, + [System.ConsoleColor] $foregroundcolor + ) -Task Clean { -} + $currentConfig = GetCurrentConfigurationOrDefault + if ($currentConfig.coloredOutput -eq $true) { + if (($Host.UI -ne $null) -and ($Host.UI.RawUI -ne $null) -and ($Host.UI.RawUI.ForegroundColor -ne $null)) { + $previousColor = $Host.UI.RawUI.ForegroundColor + $Host.UI.RawUI.ForegroundColor = $foregroundcolor + } + } -TaskSetup { - "Running 'TaskSetup' for task $script:context.Peek().currentTaskName" -} + $message -You should get the following output: ------------------------------------- - -Running 'TaskSetup' for task Clean -Executing task, Clean... -Running 'TaskSetup' for task Compile -Executing task, Compile... -Running 'TaskSetup' for task Test -Executing task, Test... - -Build Succeeded - ----------------------------------------------------------------------- -Build Time Report ----------------------------------------------------------------------- -Name Duration ----- -------- -Clean 00:00:00.0054018 -Compile 00:00:00.0123085 -Test 00:00:00.0236915 -Total: 00:00:00.0739437 - -.LINK -Invoke-psake -Include -Task -Properties -FormatTaskName -TaskTearDown -Assert -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - param( - [Parameter(Position=0,Mandatory=1)] - [scriptblock]$setup - ) - $script:context.Peek().taskSetupScriptBlock = $setup + if ($previousColor -ne $null) { + $Host.UI.RawUI.ForegroundColor = $previousColor + } } -function TaskTearDown -{ -<# -.SYNOPSIS -Adds a scriptblock that will be executed after each task - -.DESCRIPTION -This function will accept a scriptblock that will be executed after each -task in the build script. +function LoadModules { + $currentConfig = $psake.context.peek().config + if ($currentConfig.modules) { -.PARAMETER include -A scriptblock to execute -Required + $scope = $currentConfig.moduleScope -.EXAMPLE -A sample build script is shown below: + $global = [string]::Equals($scope, "global", [StringComparison]::CurrentCultureIgnoreCase) -Task default -depends Test - -Task Test -depends Compile, Clean { + $currentConfig.modules | foreach { + resolve-path $_ | foreach { + "Loading module: $_" + $module = import-module $_ -passthru -DisableNameChecking -global:$global + if (!$module) { + throw ($msgs.error_loading_module -f $_.Name) + } + } + } + "" + } } -Task Compile -depends Clean { -} +function LoadConfiguration { + param( + [string] $configdir = $PSScriptRoot + ) -Task Clean { -} + $psakeConfigFilePath = (join-path $configdir "psake-config.ps1") -TaskTearDown { - "Running 'TaskTearDown' for task $script:context.Peek().currentTaskName" + if (test-path $psakeConfigFilePath -pathType Leaf) { + try { + $config = GetCurrentConfigurationOrDefault + . $psakeConfigFilePath + } catch { + throw "Error Loading Configuration from psake-config.ps1: " + $_ + } + } } -You should get the following output: ------------------------------------- - -Executing task, Clean... -Running 'TaskTearDown' for task Clean -Executing task, Compile... -Running 'TaskTearDown' for task Compile -Executing task, Test... -Running 'TaskTearDown' for task Test - -Build Succeeded - ----------------------------------------------------------------------- -Build Time Report ----------------------------------------------------------------------- -Name Duration ----- -------- -Clean 00:00:00.0064555 -Compile 00:00:00.0218902 -Test 00:00:00.0309151 -Total: 00:00:00.0858301 - -.LINK -Invoke-psake -Include -Task -Properties -FormatTaskName -TaskSetup -Assert -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - param( - [Parameter(Position=0,Mandatory=1)] - [scriptblock]$teardown) - $script:context.Peek().taskTearDownScriptBlock = $teardown +function GetCurrentConfigurationOrDefault() { + if ($psake.context.count -gt 0) { + return $psake.context.peek().config + } else { + return $psake.config_default + } } -function Invoke-psake -{ -<# -.SYNOPSIS -Runs a psake build script. - -.DESCRIPTION -This function runs a psake build script - -.PARAMETER BuildFile -The psake build script to execute (default: default.ps1). - -.PARAMETER TaskList -A comma-separated list of task names to execute - -.PARAMETER Framework -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. -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' -Default = '3.5' - -.PARAMETER Docs -Prints a list of tasks and their descriptions - -.PARAMETER Parameters -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! - -.PARAMETER Properties -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. - -.EXAMPLE -Invoke-psake - -Runs the 'default' task in the 'default.ps1' build script in the current directory - -.EXAMPLE -Invoke-psake '.\build.ps1' - -Runs the 'default' task in the '.build.ps1' build script - -.EXAMPLE -Invoke-psake '.\build.ps1' Tests,Package +function CreateConfigurationForNewContext { + param( + [string] $buildFile, + [string] $framework + ) -Runs the 'Tests' and 'Package' tasks in the '.build.ps1' build script + $previousConfig = GetCurrentConfigurationOrDefault -.EXAMPLE -Invoke-psake Tests + $config = new-object psobject -property @{ + buildFileName = $previousConfig.buildFileName; + framework = $previousConfig.framework; + taskNameFormat = $previousConfig.taskNameFormat; + verboseError = $previousConfig.verboseError; + coloredOutput = $previousConfig.coloredOutput; + modules = $previousConfig.modules; + moduleScope = $previousConfig.moduleScope; + } -If you have your Tasks in the .\default.ps1. This example will run the 'Tests' tasks in the 'default.ps1' build script. + if ($framework) { + $config.framework = $framework; + } -.EXAMPLE -Invoke-psake 'Tests, Package' + if ($buildFile) { + $config.buildFileName = $buildFile; + } -If you have your Tasks in the .\default.ps1. This example will run the 'Tests' and 'Package' tasks in the 'default.ps1' build script. -NOTE: the quotes around the list of tasks to execute. + return $config +} -.EXAMPLE -Invoke-psake '.\build.ps1' -docs - -Prints a report of all the tasks and their descriptions and exits - -.EXAMPLE -Invoke-psake .\parameters.ps1 -parameters @{"p1"="v1";"p2"="v2"} - -Runs the build script called 'parameters.ps1' and passes in parameters 'p1' and 'p2' with values 'v1' and 'v2' - -.EXAMPLE -Invoke-psake .\properties.ps1 -properties @{"x"="1";"y"="2"} - -Runs the build script called 'properties.ps1' and passes in parameters 'x' and 'y' with values '1' and '2' - -.OUTPUTS - If there is an exception and '$psake.use_exit_on_error' -eq $true - then runs exit(1) to set the DOS lastexitcode variable - otherwise set the '$psake.build_success variable' to $true or $false depending - on whether an exception was thrown - -.NOTES -When the psake module is loaded a variabled called $psake is created it is a hashtable -containing some variables that can be used to configure psake: - -$psake.use_exit_on_error = $false # determines if psake uses the "exit()" function when an exception occurs -$psake.log_error = $false # determines if the exception details are written to a file -$psake.build_success = $false # indicates that the current build was successful -$psake.version = "4.00" # contains the current version of psake -$psake.build_script_file = $null # contains a System.IO.FileInfo for the current build file -$psake.framework_version = "" # contains the framework version # for the current build - -$psake.use_exit_on_error and $psake.log_error are boolean variables that can be set before you call Invoke-Psake. - -You should see the following when you display the contents of the $psake variable right after importing psake - -PS projects:\psake> Import-Module .\psake.psm1 -PS projects:\psake> $psake - -Name Value ----- ----- -version 4.00 -build_script_file -use_exit_on_error False -build_success False -log_error False -framework_version - -After a build is executed the following $psake values are updated (build_script_file, build_success, and framework_version) - -PS projects:\psake> Invoke-psake .\examples\default.ps1 -Executing task: Clean -Executed Clean! -Executing task: Compile -Executed Compile! -Executing task: Test -Executed Test! - -Build Succeeded! - ----------------------------------------------------------------------- -Build Time Report ----------------------------------------------------------------------- -Name Duration ----- -------- -Clean 00:00:00.0798486 -Compile 00:00:00.0869948 -Test 00:00:00.0958225 -Total: 00:00:00.2712414 - -PS projects:\psake> $psake - -Name Value ----- ----- -version 4.00 -build_script_file C:\Users\Jorge\Documents\Projects\psake\examples\default.ps1 -use_exit_on_error False -build_success True -log_error False -framework_version 3.5 - -.LINK -Task -Include -Properties -FormatTaskName -TaskSetup -TaskTearDown -Assert -#> -[CmdletBinding( - SupportsShouldProcess=$False, - SupportsTransactions=$False, - ConfirmImpact="None", - DefaultParameterSetName="")] - - param( - [Parameter(Position=0,Mandatory=0)] - [string]$buildFile = $script:psake.default_build_file_name, - [Parameter(Position=1,Mandatory=0)] - [string[]]$taskList = @(), - [Parameter(Position=2,Mandatory=0)] - [string]$framework = '3.5', - [Parameter(Position=3,Mandatory=0)] - [switch]$docs = $false, - [Parameter(Position=4,Mandatory=0)] - [System.Collections.Hashtable]$parameters = @{}, - [Parameter(Position=5, Mandatory=0)] - [System.Collections.Hashtable]$properties = @{} - ) - - Begin - { - $script:psake.build_success = $false - $script:psake.framework_version = $framework - - if ($script:context -eq $null) - { - $script:context = New-Object System.Collections.Stack +function ConfigureBuildEnvironment { + $framework = $psake.context.peek().config.framework + if ($framework -cmatch '^((?:\d+\.\d+)(?:\.\d+){0,1})(x86|x64){0,1}$') { + $versionPart = $matches[1] + $bitnessPart = $matches[2] + } else { + throw ($msgs.error_invalid_framework -f $framework) + } + $versions = $null + $buildToolsVersions = $null + switch ($versionPart) { + '1.0' { + $versions = @('v1.0.3705') + } + '1.1' { + $versions = @('v1.1.4322') + } + '2.0' { + $versions = @('v2.0.50727') + } + '3.0' { + $versions = @('v2.0.50727') + } + '3.5' { + $versions = @('v3.5', 'v2.0.50727') + } + '4.0' { + $versions = @('v4.0.30319') + } + '4.5.1' { + $versions = @('v4.0.30319') + $buildToolsVersions = @('14.0', '12.0') + } + default { + throw ($msgs.error_unknown_framework -f $versionPart, $framework) + } } - $script:context.push(@{ - "formatTaskNameString" = "Executing task: {0}"; - "taskSetupScriptBlock" = $null; - "taskTearDownScriptBlock" = $null; - "executedTasks" = New-Object System.Collections.Stack; - "callStack" = New-Object System.Collections.Stack; - "originalEnvPath" = $env:path; - "originalDirectory" = Get-Location; - "originalErrorActionPreference" = $global:ErrorActionPreference; - "tasks" = @{}; - "properties" = @(); - "includes" = New-Object System.Collections.Queue; - }) - } - - Process - { - try - { - $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - - <# - If the default.ps1 file exists and the given "buildfile" isn't found assume that the given - $buildFile is actually the target Tasks to execute in the default.ps1 script. - #> - if((Test-Path $script:psake.default_build_file_name ) -and !(test-path $buildFile)) { - $list = New-Object System.Collections.ArrayList - foreach($t in $buildFile.Split(',')) { - $t1 = $t.Trim() - if($t1 -ne $null -or $t1 -ne "") { - $list.Add($t1) + $bitness = 'Framework' + if ($versionPart -ne '1.0' -and $versionPart -ne '1.1') { + switch ($bitnessPart) { + 'x86' { + $bitness = 'Framework' + $buildToolsKey = 'MSBuildToolsPath32' + } + 'x64' { + $bitness = 'Framework64' + $buildToolsKey = 'MSBuildToolsPath' + } + { [string]::IsNullOrEmpty($_) } { + $ptrSize = [System.IntPtr]::Size + switch ($ptrSize) { + 4 { + $bitness = 'Framework' + $buildToolsKey = 'MSBuildToolsPath32' + } + 8 { + $bitness = 'Framework64' + $buildToolsKey = 'MSBuildToolsPath' + } + default { + throw ($msgs.error_unknown_pointersize -f $ptrSize) + } + } + } + default { + throw ($msgs.error_unknown_bitnesspart -f $bitnessPart, $framework) + } + } + } + $frameworkDirs = @() + if ($buildToolsVersions -ne $null) { + foreach($ver in $buildToolsVersions) { + if (Test-Path "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\$ver") { + $frameworkDirs += (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\$ver" -Name $buildToolsKey).$buildToolsKey + } + } + } + $frameworkDirs = $frameworkDirs + @($versions | foreach { "$env:windir\Microsoft.NET\$bitness\$_\" }) + + for ($i = 0; $i -lt $frameworkDirs.Count; $i++) { + $dir = $frameworkDirs[$i] + if ($dir -Match "\$\(Registry:HKEY_LOCAL_MACHINE(.*?)@(.*)\)") { + $key = "HKLM:" + $matches[1] + $name = $matches[2] + $dir = (Get-ItemProperty -Path $key -Name $name).$name + $frameworkDirs[$i] = $dir } - } - $taskList = $list.ToArray() - $buildFile = $script:psake.default_build_file_name } - # Execute the build file to set up the tasks and defaults - Assert (test-path $buildFile) "Error: Could not find the build file, $buildFile." + $frameworkDirs | foreach { Assert (test-path $_ -pathType Container) ($msgs.error_no_framework_install_dir_found -f $_)} - $script:psake.build_script_file = dir $buildFile - set-location $script:psake.build_script_file.Directory - . $script:psake.build_script_file.FullName + $env:path = ($frameworkDirs -join ";") + ";$env:path" + # if any error occurs in a PS function then "stop" processing immediately + # this does not effect any external programs that return a non-zero exit code + $global:ErrorActionPreference = "Stop" +} - if ($docs) - { - Write-Documentation - Cleanup-Environment - return +function CleanupEnvironment { + if ($psake.context.Count -gt 0) { + $currentContext = $psake.context.Peek() + $env:path = $currentContext.originalEnvPath + Set-Location $currentContext.originalDirectory + $global:ErrorActionPreference = $currentContext.originalErrorActionPreference + [void] $psake.context.Pop() } +} - Configure-BuildEnvironment - - # N.B. The initial dot (.) indicates that variables initialized/modified - # in the propertyBlock are available in the parent scope. - while ($script:context.Peek().includes.Count -gt 0) - { - $includeBlock = $script:context.Peek().includes.Dequeue() - . $includeBlock - } +function SelectObjectWithDefault +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$true)] + [PSObject] + $InputObject, + [string] + $Name, + $Value + ) - foreach($key in $parameters.keys) - { - if (test-path "variable:\$key") - { - set-item -path "variable:\$key" -value $parameters.$key | out-null - } - else - { - new-item -path "variable:\$key" -value $parameters.$key | out-null - } + process { + if ($_ -eq $null) { $Value } + elseif ($_ | Get-Member -Name $Name) { + $_.$Name + } + elseif (($_ -is [Hashtable]) -and ($_.Keys -contains $Name)) { + $_.$Name + } + else { $Value } } +} - foreach($propertyBlock in $script:context.Peek().properties) - { - . $propertyBlock - } +# borrowed from Jeffrey Snover http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx +# modified to better handle SQL errors +function ResolveError +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$true)] + $ErrorRecord=$Error[0], + [Switch] + $Short + ) - foreach($key in $properties.keys) - { - if (test-path "variable:\$key") - { - set-item -path "variable:\$key" -value $properties.$key | out-null - } - } + process { + if ($_ -eq $null) { $_ = $ErrorRecord } + $ex = $_.Exception + + if (-not $Short) { + $error_message = "`nErrorRecord:{0}ErrorRecord.InvocationInfo:{1}Exception:`n{2}" + $formatted_errorRecord = $_ | format-list * -force | out-string + $formatted_invocationInfo = $_.InvocationInfo | format-list * -force | out-string + $formatted_exception = '' + + $i = 0 + while ($ex -ne $null) { + $i++ + $formatted_exception += ("$i" * 70) + "`n" + + ($ex | format-list * -force | out-string) + "`n" + $ex = $ex | SelectObjectWithDefault -Name 'InnerException' -Value $null + } + + return $error_message -f $formatted_errorRecord, $formatted_invocationInfo, $formatted_exception + } - # Execute the list of tasks or the default task - if($taskList.Length -ne 0) - { - foreach($task in $taskList) - { - ExecuteTask $task - } - } - elseif ($script:context.Peek().tasks.default -ne $null) - { - ExecuteTask default - } - else - { - throw 'Error: default task required' + $lastException = @() + while ($ex -ne $null) { + $lastMessage = $ex | SelectObjectWithDefault -Name 'Message' -Value '' + $lastException += ($lastMessage -replace "`n", '') + if ($ex -is [Data.SqlClient.SqlException]) { + $lastException += "(Line [$($ex.LineNumber)] " + + "Procedure [$($ex.Procedure)] Class [$($ex.Class)] " + + " Number [$($ex.Number)] State [$($ex.State)] )" + } + $ex = $ex | SelectObjectWithDefault -Name 'InnerException' -Value $null + } + $shortException = $lastException -join ' --> ' + + $header = $null + $current = $_ + $header = (($_.InvocationInfo | + SelectObjectWithDefault -Name 'PositionMessage' -Value '') -replace "`n", ' '), + ($_ | SelectObjectWithDefault -Name 'Message' -Value ''), + ($_ | SelectObjectWithDefault -Name 'Exception' -Value '') | + ? { -not [String]::IsNullOrEmpty($_) } | + Select -First 1 + + $delimiter = '' + if ((-not [String]::IsNullOrEmpty($header)) -and + (-not [String]::IsNullOrEmpty($shortException))) + { $delimiter = ' [<<==>>] ' } + + return "$($header)$($delimiter)Exception: $($shortException)" } +} - $stopwatch.Stop() +function WriteDocumentation($showDetailed) { + $currentContext = $psake.context.Peek() - "`nBuild Succeeded!`n" + if ($currentContext.tasks.default) { + $defaultTaskDependencies = $currentContext.tasks.default.DependsOn + } else { + $defaultTaskDependencies = @() + } - Write-TaskTimeSummary + $docs = $currentContext.tasks.Keys | foreach-object { + if ($_ -eq "default") { + return + } - $script:psake.build_success = $true + $task = $currentContext.tasks.$_ + new-object PSObject -property @{ + Name = $task.Name; + Alias = $task.Alias; + Description = $task.Description; + "Depends On" = $task.DependsOn -join ", " + Default = if ($defaultTaskDependencies -contains $task.Name) { $true } + } } - catch - { - #Append detailed exception and script variables to error log file - if ($script:psake.log_error) - { - $errorLogFile = "psake-error-log-{0}.log" -f ([DateTime]::Now.ToString("yyyyMMdd")) - "-" * 70 >> $errorLogFile - "{0}: An Error Occurred. See Error Details Below: " -f [DateTime]::Now >>$errorLogFile - "-" * 70 >> $errorLogFile - Resolve-Error $_ >> $errorLogFile - "-" * 70 >> $errorLogFile - "Script Variables" >> $errorLogFile - "-" * 70 >> $errorLogFile - Get-Variable -scope script >> $errorLogFile + if ($showDetailed) { + $docs | sort 'Name' | format-list -property Name,Alias,Description,"Depends On",Default + } else { + $docs | sort 'Name' | format-table -autoSize -wrap -property Name,Alias,"Depends On",Default,Description } - $buildFileName = Split-Path $buildFile -leaf - if (test-path $buildFile) { $buildFileName = $script:psake.build_script_file.Name } - Write-Host -foregroundcolor Red ($buildFileName + ":" + $_) +} - if ($script:psake.use_exit_on_error) - { - exit(1) - } - else - { - $script:psake.build_success = $false - } +function WriteTaskTimeSummary($invokePsakeDuration) { + if ($psake.context.count -gt 0) { + "-" * 70 + "Build Time Report" + "-" * 70 + $list = @() + $currentContext = $psake.context.Peek() + while ($currentContext.executedTasks.Count -gt 0) { + $taskKey = $currentContext.executedTasks.Pop() + $task = $currentContext.tasks.$taskKey + if ($taskKey -eq "default") { + continue + } + $list += new-object PSObject -property @{ + Name = $task.Name; + Duration = $task.Duration + } + } + [Array]::Reverse($list) + $list += new-object PSObject -property @{ + Name = "Total:"; + Duration = $invokePsakeDuration + } + # using "out-string | where-object" to filter out the blank line that format-table prepends + $list | format-table -autoSize -property Name,Duration | out-string -stream | where-object { $_ } } - } #Process - - End - { - # Clear out any global variables - Cleanup-Environment - [void]$script:context.Pop() - } } -Export-ModuleMember -Function "Invoke-psake","Task","Properties","Include","FormatTaskName","TaskSetup","TaskTearDown","Assert","Exec" \ No newline at end of file +DATA msgs { +convertfrom-stringdata @' + error_invalid_task_name = Task name should not be null or empty string. + error_task_name_does_not_exist = Task {0} does not exist. + error_circular_reference = Circular reference found for task {0}. + error_missing_action_parameter = Action parameter must be specified when using PreAction or PostAction parameters for task {0}. + error_corrupt_callstack = Call stack was corrupt. Expected {0}, but got {1}. + error_invalid_framework = Invalid .NET Framework version, {0} specified. + error_unknown_framework = Unknown .NET Framework version, {0} specified in {1}. + error_unknown_pointersize = Unknown pointer size ({0}) returned from System.IntPtr. + error_unknown_bitnesspart = Unknown .NET Framework bitness, {0}, specified in {1}. + error_no_framework_install_dir_found = No .NET Framework installation directory found at {0}. + error_bad_command = Error executing command {0}. + error_default_task_cannot_have_action = 'default' task cannot specify an action. + error_duplicate_task_name = Task {0} has already been defined. + error_duplicate_alias_name = Alias {0} has already been defined. + error_invalid_include_path = Unable to include {0}. File not found. + error_build_file_not_found = Could not find the build file {0}. + error_no_default_task = 'default' task required. + error_loading_module = Error loading module {0}. + warning_deprecated_framework_variable = Warning: Using global variable $framework to set .NET framework version used is deprecated. Instead use Framework function or configuration file psake-config.ps1. + required_variable_not_set = Variable {0} must be set to run task {1}. + postcondition_failed = Postcondition failed for task {0}. + precondition_was_false = Precondition was false, not executing task {0}. + continue_on_error = Error in task {0}. {1} + build_success = Build Succeeded! +'@ +} + +import-localizeddata -bindingvariable msgs -erroraction silentlycontinue + +$script:psake = @{} +$psake.version = "4.4.1" # contains the current version of psake +$psake.context = new-object system.collections.stack # holds onto the current state of all variables +$psake.run_by_psake_build_tester = $false # indicates that build is being run by psake-BuildTester +$psake.config_default = new-object psobject -property @{ + buildFileName = "default.ps1"; + framework = "4.0"; + taskNameFormat = "Executing {0}"; + verboseError = $false; + coloredOutput = $true; + modules = $null; + moduleScope = ""; +} # contains default configuration, can be overriden in psake-config.ps1 in directory with psake.psm1 or in directory with current build script + +$psake.build_success = $false # indicates that the current build was successful +$psake.build_script_file = $null # contains a System.IO.FileInfo for the current build script +$psake.build_script_dir = "" # contains a string with fully-qualified path to current build script + +LoadConfiguration + +export-modulemember -function Invoke-psake, Invoke-Task, Task, Properties, Include, FormatTaskName, TaskSetup, TaskTearDown, Framework, Assert, Exec -variable psake