Skip to content

3F/E-MSBuild

Repository files navigation

EvMSBuild

Advanced Evaluator of MSBuild scripts aka Advanced MSBuild with user-variables support through Varhead and more.

It is actively used in projects such as vsSolutionBuildEvent, SobaScript, vsCommandEvent,

Build status release-src License NuGet package Tests

Build history

License

Licensed under the MIT License

Copyright (c) 2013-2020  Denis Kuzmin < x-3F@outlook.com > GitHub/3F

[ ☕ Donate ]

E-MSBuild contributors https://github.com/3F/E-MSBuild/graphs/contributors

Syntax and features

evaluation region (container) optional scope escaping
$( … ) $( …:project ) $$( … )

User Variables:

$(name = $( ))

Strings:

  • "…" - Allows any evaluation inside string. Escaping is a \"
  • '…' - All containers $() should not be evaluated for this type string. Escaping is a \'

'' and "" used 'as is' for compatibility with MSBuild

$(name = "  - Platform is a $(Platform)  ")
$(sPoint = $([System.DateTime]::Parse("2019/08/01").ToBinary()))
$(pdir = $(ProjectDir.Replace('\', '/'):project))

Increment & Decrement operators for numbers and strings

Numbers Strings
$(i += 1) $(name += "str")
$(i -= 1)

+= and -= for numbers also initializes variable to 0 if it's not defined before.

$(n = 0)
$(n += 3.14)
$(n += $(n))
$(desc = "Hello ")
$(desc += "world !")

Math operations

$(numYmod = $([MSBuild]::Modulo($(numY), 12)))
$([MSBuild]::BitwiseAnd($(mask), $(v))) != 0

Difference between quotes. Exponential notation problem

All data inside double quotes ".." will be evaluated manually (standard moving: upward from deepest).

All data inside single quotes '..' is not processed and will be entirely sent into engine for a single final evaluation.

What does this mean, for example:

    $([MSBuild]::Multiply("$([System.Math]::Log(2))", 16)) -> 1,10903548889591E+16
    \                     \_(1) 0,693147180559945_/
    \_______________(2)__________________________________/


    $([MSBuild]::Multiply('$([System.Math]::Log(2))', 16)) -> 11,0903548889591
    \______________________(1)___________________________/

$([System.Math]::Exp(1.10903548889591E+16)) = ∞ 
$([System.Math]::Exp(11.0903548889591)) = 65535,9999999983

Other samples:

Expression Evaluated value
$([System.Math]::Log(2)) 0,693147180559945
$([MSBuild]::Multiply('$([System.Math]::Log(2))', 16)) 11,0903548889591
$([System.Math]::Exp('$([MSBuild]::Multiply($([System.Math]::Log(2)), 16))')) 65536
$([System.Math]::Exp($([MSBuild]::Multiply('$([System.Math]::Log(10))', 4)))) 
= 9999.99999999997

$([System.Math]::Exp($([MSBuild]::Multiply($([System.Math]::Log(10)), 4)))) 
= 10000.0000000002

$([System.Math]::Exp('$([MSBuild]::Multiply($([System.Math]::Log(10)), 4))')) 
= 10000
#[$(
    [System.Math]::Exp('$(
        [MSBuild]::Multiply(
            $([System.Math]::Log(10)), 
            4
        ))'
    )
)]
= 10000

Min / Max

0 - n & n - 18:

$([System.Math]::Max(0, $(n)))
$([System.Math]::Min($(n), 18))

n - m (min(max($(n), $(val)), $(m))):

$([System.Math]::Min( $([System.Math]::Max( $(n), $(val) )), $(m) ))

Bit mask

Set

$(mask = 0)

$(mask = $([MSBuild]::BitwiseOr($(mask), 1)))
$(mask = $([MSBuild]::BitwiseOr($(mask), 4)))
$(mask = $([MSBuild]::BitwiseOr($(mask), 8)))

$(maskString = $([System.Convert]::ToString('$([System.Convert]::ToInt32($(mask)))', 2)))

Result: 1101

Check

#[$(v = 2)]

#[( $([MSBuild]::BitwiseAnd($(mask), $(v))) != 0 ){ 
    "$(v)" is defined in the mask($(maskString)) 
}
else{ 
    "$(v)" is not defined in the mask($(maskString))
}]

"2" is not defined in the mask(1101)

The numbers of modulo

0 - 99:

$([MSBuild]::Modulo($(num), 100))
0, 1, 2, 3, 4 ... 98, 99, 0, 1, 2 ...

n - m (e.g. 10 - 99):

Same as above, only use limit like:

 = (val % (max - min)) + min
#[$(
    [MSBuild]::Add(
        $(minrev), 
        $([MSBuild]::Modulo(
            $(num), 
            $([MSBuild]::Subtract(
                $(maxrev), 
                $(minrev)
             ))
         ))
    )
)]
10, 11, 12, ... 98, 99, 10, 11, 12 ...

Raise number to the specified power

$([System.Math]::Pow(10, 4)) 
= 10000

or via exp:

$([System.Math]::Exp('$([MSBuild]::Multiply($([System.Math]::Log(10)), 4))'))
= 10000

Nested levels. Recursive evaluation

$($(…:$(…)))
$($(…:$($(…:$(…)))))

Useful for any dynamic references to your data or additional evaluation. For example:

$(ProjectDir:$(ProjectName)) - ProjectDir value scoped by project name at runtime

Global properties

To use local scoped variables as part of other msbuild properties and so on.

$(+name = …)
$(+MyProperty = "val1")

To unset:

$(-name =)
$(-name = …)

Operations with strings

$(SolutionPath.Replace('\', '/'))  -> to D:/App/ConsoleApp1.sln
$(SolutionPath.Replace('\', '\\')) -> to D:\\App\\ConsoleApp1.sln
$(desc = "Hello ")
$(desc = $([System.String]::Concat($(desc), "world !")) )

Escape-Sequence

Implemented a strictly limited set:

$([System.String]::Concat("\r\n"))
$(ver = "1.2.3")
$([System.String]::Format("\t version is a {0}", $(ver)))

Newline characters and other problematic symbols

$(cs.Replace("\r\n", ""))

or other CR/LF combination (Newline - representations)

$(cs.Replace("\r", "").Replace("\n", ""))

Date & Time

Format & Culture:

Sortable format: ~ yyyy/MM/dd, eg.: 2016/08/21 for InvariantCulture

For specific culture use for example:

$([System.DateTime]::Parse("21.08.2016", '$([System.Globalization.CultureInfo]::GetCultureInfo("ru-RU"))'))
$([System.DateTime]::Parse("08/21/2016", '$([System.Globalization.CultureInfo]::GetCultureInfo("en-US"))'))

Number of ticks:

$([System.DateTime]::Parse("2015/02/17").ToBinary())
$([System.DateTime]::Parse("2015/02/17 07:21").ToBinary())

UtcNow.Ticks

$([System.DateTime]::UtcNow.Ticks)

Total Minutes or Hours from Ticks:

$([System.TimeSpan]::FromTicks(635618792404338780).TotalHours)
$([System.TimeSpan]::FromTicks(635618792404338780).TotalMinutes)

Delta between the time:

$([System.TimeSpan]::FromTicks($([MSBuild]::Subtract(635618821282084745, 635618792404338780))).TotalMinutes.ToString("0"))
$([System.TimeSpan]::FromTicks($([MSBuild]::Subtract(635618821282084745, 635618792404338780))).TotalSeconds.ToString("0"))

Custom Date and Time Format Strings:

$([System.DateTime]::UtcNow.ToString("yyyy.MM.dd_HH;mm;ss.ffff"))

Result: 2016.02.07_10;56;54.8265

Standard MSBuild Property Functions support

Property Functions:

$([System.Guid]::NewGuid()) 
$(SolutionDir.Substring(0,3))
$([System.DateTime]::Now.ToString("yyyy.MM.dd HH:mm:ss"))  

Registry Properties:

$(registry:Hive\MyKey\MySubKey@ValueName) - gets value for ValueName from subkey.
$(registry:Hive\MyKey\MySubKey) - gets the default subkey value.