# Use the .NET Core APIs to determine the current platform; if a runtime # exception is thrown, we are on FullCLR, not .NET Core. try { $Runtime = [System.Runtime.InteropServices.RuntimeInformation] $OSPlatform = [System.Runtime.InteropServices.OSPlatform] $IsCore = $true $IsLinux = $Runtime::IsOSPlatform($OSPlatform::Linux) $IsOSX = $Runtime::IsOSPlatform($OSPlatform::OSX) $IsWindows = $Runtime::IsOSPlatform($OSPlatform::Windows) } catch { # If these are already set, then they're read-only and we're done try { $IsCore = $false $IsLinux = $false $IsOSX = $false $IsWindows = $true } catch { } } if ($IsLinux) { $LinuxInfo = Get-Content /etc/os-release | ConvertFrom-StringData } function Start-PSBuild { [CmdletBinding(DefaultParameterSetName='CoreCLR')] param( [switch]$NoPath, [switch]$Restore, [string]$Output, [switch]$ResGen, [switch]$TypeGen, [switch]$Clean, [Parameter(ParameterSetName='CoreCLR')] [switch]$Publish, # These runtimes must match those in project.json # We do not use ValidateScript since we want tab completion [ValidateSet("ubuntu.14.04-x64", "debian.8-x64", "centos.7-x64", "win7-x64", "win81-x64", "win10-x64", "osx.10.11-x64")] [Parameter(ParameterSetName='CoreCLR')] [string]$Runtime, [Parameter(ParameterSetName='FullCLR', Mandatory=$true)] [switch]$FullCLR, [Parameter(ParameterSetName='FullCLR')] [switch]$XamlGen, [Parameter(ParameterSetName='FullCLR')] [string]$cmakeGenerator = "Visual Studio 14 2015 Win64", [Parameter(ParameterSetName='FullCLR')] [ValidateSet("Debug", "Release")] [string]$msbuildConfiguration = "Release" ) if ($Clean) { log "Cleaning your working directory. You can also do it with 'git clean -fdX'" git clean -fdX } # save Git description to file for PowerShell to include in PSVersionTable git --git-dir="$PSScriptRoot/.git" describe --dirty --abbrev=60 > "$psscriptroot/powershell.version" # simplify ParameterSetNames if ($PSCmdlet.ParameterSetName -eq 'FullCLR') { $FullCLR = $true } # Add .NET CLI tools to PATH Find-Dotnet if ($IsWindows) { # use custom package store - this value is also defined in nuget.config under config/repositoryPath # dotnet restore uses this value as the target for installing the assemblies for referenced nuget packages. # dotnet build does not currently consume the config value but will consume env:NUGET_PACKAGES to resolve these dependencies $env:NUGET_PACKAGES="$PSScriptRoot\Packages" } # verify we have all tools in place to do the build $precheck = precheck 'dotnet' "Build dependency 'dotnet' not found in PATH. Run Start-PSBootstrap. Also see: https://dotnet.github.io/getting-started/" if ($FullCLR) { # cmake is needed to build powershell.exe $precheck = $precheck -and (precheck 'cmake' 'cmake not found. You can install it from https://chocolatey.org/packages/cmake.portable') Use-MSBuild #mc.exe is Message Compiler for native resources $mcexe = Get-ChildItem "${env:ProgramFiles(x86)}\Microsoft SDKs\Windows\" -Recurse -Filter 'mc.exe' | ? {$_.FullName -match 'x64'} | select -First 1 | % {$_.FullName} if (-not $mcexe) { throw 'mc.exe not found. Install Microsoft Windows SDK.' } } elseif ($IsLinux -or $IsOSX) { foreach ($Dependency in 'cmake', 'make', 'g++') { $precheck = $precheck -and (precheck $Dependency "Build dependency '$Dependency' not found. Run Start-PSBootstrap.") } } # Abort if any precheck failed if (-not $precheck) { return } # set output options $OptionsArguments = @{Publish=$Publish; Output=$Output; FullCLR=$FullCLR; Runtime=$Runtime} $script:Options = New-PSOptions @OptionsArguments # setup arguments $Arguments = @() if ($Publish) { $Arguments += "publish" } else { $Arguments += "build" } if ($Output) { $Arguments += "--output", (Join-Path $PSScriptRoot $Output) } $Arguments += "--configuration", $Options.Configuration $Arguments += "--framework", $Options.Framework $Arguments += "--runtime", $Options.Runtime # handle Restore if ($Restore -or -not (Test-Path "$($Options.Top)/project.lock.json")) { log "Run dotnet restore" $RestoreArguments = @("--verbosity") if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { $RestoreArguments += "Info" } else { $RestoreArguments += "Warning" } $RestoreArguments += "$PSScriptRoot" Start-NativeExecution { dotnet restore $RestoreArguments } } # handle ResGen # Heuristic to run ResGen on the fresh machine if ($ResGen -or -not (Test-Path "$PSScriptRoot/src/Microsoft.PowerShell.ConsoleHost/gen")) { log "Run ResGen (generating C# bindings for resx files)" Start-ResGen } # handle xaml files # Heuristic to resolve xaml on the fresh machine if ($FullCLR -and ($XamlGen -or -not (Test-Path "$PSScriptRoot/src/Microsoft.PowerShell.Activities/gen/*.g.cs"))) { log "Run XamlGen (generating .g.cs and .resources for .xaml files)" Start-XamlGen -MSBuildConfiguration $msbuildConfiguration } # Build native components if ($IsLinux -or $IsOSX) { $Ext = if ($IsLinux) { "so" } elseif ($IsOSX) { "dylib" } $Native = "$PSScriptRoot/src/libpsl-native" $Lib = "$($Options.Top)/libpsl-native.$Ext" log "Start building $Lib" try { Push-Location $Native cmake -DCMAKE_BUILD_TYPE=Debug . make -j make test } finally { Pop-Location } if (-not (Test-Path $Lib)) { throw "Compilation of $Lib failed" } } elseif ($FullCLR) { log "Start building native powershell.exe" try { Push-Location "$PSScriptRoot\src\powershell-native" # Compile native resources @("nativemsh/pwrshplugin") | % { $nativeResourcesFolder = $_ Get-ChildItem $nativeResourcesFolder -Filter "*.mc" | % { # & $mcexe -c -U $_.FullName -h $nativeResourcesFolder -r $nativeResourcesFolder } } if ($cmakeGenerator) { Start-NativeExecution { cmake -G $cmakeGenerator . } } else { Start-NativeExecution { cmake . } } Start-NativeExecution { msbuild powershell.vcxproj /p:Configuration=$msbuildConfiguration } } finally { Pop-Location } } # handle TypeGen if ($TypeGen -or -not (Test-Path "$PSScriptRoot/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CorePsTypeCatalog.cs")) { log "Run TypeGen (generating CorePsTypeCatalog.cs)" Start-TypeGen } try { # Relative paths do not work well if cwd is not changed to project Push-Location $Options.Top log "Run dotnet $Arguments from $pwd" Start-NativeExecution { dotnet $Arguments } log "PowerShell output: $($Options.Output)" } finally { Pop-Location } } function New-PSOptions { [CmdletBinding()] param( [ValidateSet("Linux", "Debug", "Release")] [string]$Configuration, [ValidateSet("netcoreapp1.0", "net451")] [string]$Framework, # These are duplicated from Start-PSBuild # We do not use ValidateScript since we want tab completion [ValidateSet("", "ubuntu.14.04-x64", "debian.8-x64", "centos.7-x64", "win7-x64", "win81-x64", "win10-x64", "osx.10.11-x64")] [string]$Runtime, [switch]$Publish, [string]$Output, [switch]$FullCLR ) # Add .NET CLI tools to PATH Find-Dotnet if ($FullCLR) { $Top = "$PSScriptRoot/src/Microsoft.PowerShell.ConsoleHost" } else { $Top = "$PSScriptRoot/src/powershell" } Write-Verbose "Top project directory is $Top" if (-not $Configuration) { $Configuration = if ($IsLinux -or $IsOSX) { "Linux" } elseif ($IsWindows) { "Debug" } log "Using configuration '$Configuration'" } if (-not $Framework) { $Framework = if ($FullCLR) { "net451" } else { "netcoreapp1.0" } log "Using framework '$Framework'" } if (-not $Runtime) { $Runtime = dotnet --info | % { if ($_ -match "RID") { $_ -split "\s+" | Select-Object -Last 1 } } if (-not $Runtime) { Throw "Could not determine Runtime Identifier, please update dotnet" } else { log "Using runtime '$Runtime'" } } $Executable = if ($IsLinux -or $IsOSX) { "powershell" } elseif ($IsWindows) { "powershell.exe" } # Build the Output path if ($Output) { $Output = Join-Path $PSScriptRoot $Output } else { $Output = [IO.Path]::Combine($Top, "bin", $Configuration, $Framework) # FullCLR only builds a library, so there is no runtime component if (-not $FullCLR) { $Output = [IO.Path]::Combine($Output, $Runtime) } # Publish injects the publish directory if ($Publish) { $Output = [IO.Path]::Combine($Output, "publish") } $Output = [IO.Path]::Combine($Output, $Executable) } return @{ Top = $Top; Configuration = $Configuration; Framework = $Framework; Runtime = $Runtime; Output = $Output } } function Get-PSOutput { [CmdletBinding()]param( [hashtable]$Options ) if ($Options) { return $Options.Output } elseif ($script:Options) { return $script:Options.Output } else { return (New-PSOptions).Output } } function Start-PSPester { [CmdletBinding()]param( [string]$Flags = "-ExcludeTag 'Slow' -EnableExit -OutputFile pester-tests.xml -OutputFormat NUnitXml", [string]$Tests = "*", [ValidateScript({ Test-Path -PathType Container $_})] [string]$Directory = "$PSScriptRoot/test/powershell" ) & (Get-PSOutput) -noprofile -c "Import-Module '$PSScriptRoot/src/Modules/Pester'; Invoke-Pester $Flags $Directory/$Tests" if ($LASTEXITCODE -ne 0) { throw "$LASTEXITCODE Pester tests failed" } } function Start-PSxUnit { [CmdletBinding()]param() if ($IsWindows) { throw "xUnit tests are only currently supported on Linux / OS X" } if ($IsOSX) { log "Not yet supported on OS X, pretending they passed..." return } # Add .NET CLI tools to PATH Find-Dotnet $Arguments = "--configuration", "Linux", "-parallel", "none" if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { $Arguments += "-verbose" } $Content = Split-Path -Parent (Get-PSOutput) if (-not (Test-Path $Content)) { throw "PowerShell must be built before running tests!" } try { Push-Location $PSScriptRoot/test/csharp # Path manipulation to obtain test project output directory $Output = Join-Path $pwd ((Split-Path -Parent (Get-PSOutput)) -replace (New-PSOptions).Top) Write-Verbose "Output is $Output" Copy-Item -ErrorAction SilentlyContinue -Recurse -Path $Content/* -Include Modules,libpsl-native* -Destination $Output Start-NativeExecution { dotnet test $Arguments } if ($LASTEXITCODE -ne 0) { throw "$LASTEXITCODE xUnit tests failed" } } finally { Pop-Location } } function Start-PSBootstrap { [CmdletBinding()]param( [ValidateSet("dev", "beta", "preview")] [string]$Channel = "rel-1.0.0", [string]$Version = "latest" ) log "Installing Open PowerShell build dependencies" Push-Location $PSScriptRoot/tools try { # Install dependencies for Linux and OS X if ($IsLinux) { if ($LinuxInfo.ID -match 'ubuntu' -and $LinuxInfo.VERSION_ID -match '14.04') { # Install ours and .NET's dependencies sudo apt-get install -y -qq curl make g++ cmake libc6 libgcc1 libstdc++6 libcurl3 libgssapi-krb5-2 libicu52 liblldb-3.6 liblttng-ust0 libssl1.0.0 libunwind8 libuuid1 zlib1g clang-3.5 } elseif ($LinuxInfo.ID -match 'centos' -and $LinuxInfo.VERSION_ID -match '7') { sudo yum install -y -q curl make gcc-c++ cmake glibc libgcc libstdc++ libcurl krb5-libs libicu lldb openssl-libs libunwind libuuid zlib clang } else { Write-Warning "This script only supports Ubuntu 14.04 and CentOS 7, you must install dependencies manually!" } } elseif ($IsOSX) { precheck 'brew' "Bootstrap dependency 'brew' not found, must install Homebrew! See http://brew.sh/" # Install ours and .NET's dependencies brew install curl cmake openssl brew link --force openssl } $obtainUrl = "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain" # Install for Linux and OS X if ($IsLinux -or $IsOSX) { # Uninstall all previous dotnet packages $uninstallScript = if ($IsUbuntu) { "dotnet-uninstall-debian-packages.sh" } elseif ($IsOSX) { "dotnet-uninstall-pkgs.sh" } if ($uninstallScript) { curl -s $obtainUrl/uninstall/$uninstallScript -o $uninstallScript chmod +x $uninstallScript sudo ./$uninstallScript } else { Write-Warning "This script only removes prior versions of dotnet for Ubuntu 14.04 and OS X" } # Install new dotnet 1.0.0 preview packages $installScript = "dotnet-install.sh" curl -s $obtainUrl/$installScript -o $installScript chmod +x $installScript bash ./$installScript -c $Channel -v $Version } # Install for Windows if ($IsWindows -and -not $IsCore) { Remove-Item -ErrorAction SilentlyContinue -Recurse -Force ~\AppData\Local\Microsoft\dotnet $installScript = "dotnet-install.ps1" Invoke-WebRequest -Uri $obtainUrl/$installScript -OutFile $installScript & ./$installScript -c $Channel -v $Version } elseif ($IsWindows) { Write-Warning "Start-PSBootstrap cannot be run in Core PowerShell on Windows (need Invoke-WebRequest!)" } } finally { Pop-Location } } function Start-PSPackage { [CmdletBinding()]param( # PowerShell packages use Semantic Versioning http://semver.org/ [string]$Version, # Package iteration version (rarely changed) [int]$Iteration = 1, # Ubuntu, CentOS, and OS X packages are supported [ValidateSet("deb", "osxpkg", "rpm")] [string]$Type ) $Description = @" Open PowerShell on .NET Core PowerShell is an open-source, cross-platform, scripting language and rich object shell. Built upon .NET Core, it is also a C# REPL. "@ # Use Git tag if not given a version if (-not $Version) { $Version = (git --git-dir="$PSScriptRoot/.git" describe) -Replace '^v' } $Source = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -Publish)) Write-Verbose "Packaging $Source" if ($IsWindows) { $msiPackagePath = New-MSIPackage -ProductSourcePath $Source -ProductVersion $Version -AssetsPath "$PSScriptRoot\Assets" -Verbose $appxPackagePath = New-AppxPackage -PackageVersion $Version -SourcePath $Source -AssetsPath "$PSScriptRoot\Assets" -Verbose $packages = @($msiPackagePath, $appxPackagePath) return $packages } if (-not (Get-Command "fpm" -ErrorAction SilentlyContinue)) { throw "Build dependency 'fpm' not found in PATH! See: https://github.com/jordansissel/fpm" } # Decide package output type if (-not $Type) { $Type = if ($IsLinux) { if ($LinuxInfo.ID -match 'ubuntu') { "deb" } elseif ($LinuxInfo.ID -match 'centos') { "rpm" } else { throw "Building packages for $($LinuxInfo.PRETTY_NAME) is unsupported!" } } elseif ($IsOSX) { 'osxpkg' } Write-Warning "-Type was not specified, continuing with $Type" } # Follow the Filesystem Hierarchy Standard for Linux and OS X $Destination = if ($IsLinux) { "/opt/microsoft/powershell" } elseif ($IsOSX) { "/usr/local/microsoft/powershell" } # Destination for symlink to powershell executable $Link = if ($IsLinux) { "/usr/bin" } elseif ($IsOSX) { "/usr/local/bin" } New-Item -Force -ItemType SymbolicLink -Path /tmp/powershell -Target $Destination/powershell >$null # there is a weired bug in fpm # if the target of the powershell symlink exists, `fpm` aborts # with a `utime` error on OS X. # so we move it to make symlink broken $symlink_dest = "$Destination/powershell" $hack_dest = "./_fpm_symlink_hack_powershell" if ($IsOSX) { if (Test-Path $symlink_dest) { Write-Warning "Move $symlink_dest to $hack_dest (fpm utime bug)" Move-Item $symlink_dest $hack_dest } } # Change permissions for packaging chmod -R go=u $Source /tmp/powershell $libunwind = switch ($Type) { "deb" { "libunwind8" } "rpm" { "libunwind" } } $libicu = switch ($Type) { "deb" { "libicu52" } "rpm" { "libicu" } } $Arguments = @( "--force", "--verbose", "--name", "powershell", "--version", $Version, "--iteration", $Iteration, "--maintainer", "Andrew Schwartzmeyer ", "--vendor", "Microsoft ", "--url", "https://github.com/PowerShell/PowerShell", "--license", "Unlicensed", "--description", $Description, "--category", "shells", "--rpm-os", "linux", "--depends", $libunwind, "--depends", $libicu, "--deb-build-depends", "dotnet", "--deb-build-depends", "cmake", "--deb-build-depends", "g++", "-t", $Type, "-s", "dir", "$Source/=$Destination/", "/tmp/powershell=$Link" ) # Build package fpm $Arguments if ($IsOSX) { # this is continuation of a fpm hack for a weired bug if (Test-Path $hack_dest) { Write-Warning "Move $hack_dest to $symlink_dest (fpm utime bug)" Move-Item $hack_dest $symlink_dest } } } function Publish-NuGetFeed { param( [string]$OutputPath = "$PSScriptRoot/nuget-artifacts", [Parameter(Mandatory=$true)] [string]$VersionSuffix ) # Add .NET CLI tools to PATH Find-Dotnet @( 'Microsoft.PowerShell.Commands.Management', 'Microsoft.PowerShell.Commands.Utility', 'Microsoft.PowerShell.ConsoleHost', 'Microsoft.PowerShell.PSReadLine', 'Microsoft.PowerShell.Security', 'System.Management.Automation' ) | % { if ($VersionSuffix) { dotnet pack "src/$_" --output $OutputPath --version-suffix $VersionSuffix } else { dotnet pack "src/$_" --output $OutputPath } } } function Start-DevPSGitHub { param( [switch]$ZapDisable, [string[]]$ArgumentList = '', [switch]$LoadProfile, [string]$binDir = "$PSScriptRoot\src\Microsoft.PowerShell.ConsoleHost\bin\Debug\net451", [switch]$NoNewWindow ) try { if ($LoadProfile -eq $false) { $ArgumentList = @('-noprofile') + $ArgumentList } $env:DEVPATH = $binDir if ($ZapDisable) { $env:COMPLUS_ZapDisable = 1 } if (-not (Test-Path $binDir\powershell.exe.config)) { $configContents = @" "@ $configContents | Out-File -Encoding Ascii $binDir\powershell.exe.config } # splatting for the win $startProcessArgs = @{ FilePath = "$binDir\powershell.exe" ArgumentList = "$ArgumentList" } if ($NoNewWindow) { $startProcessArgs.NoNewWindow = $true $startProcessArgs.Wait = $true } Start-Process @startProcessArgs } finally { ri env:DEVPATH if ($ZapDisable) { ri env:COMPLUS_ZapDisable } } } <# .EXAMPLE PS C:> Copy-MappedFiles -PslMonadRoot .\src\monad copy files FROM .\src\monad (old location of submodule) TO src/ folders #> function Copy-MappedFiles { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [string[]]$Path = "$PSScriptRoot", [Parameter(Mandatory=$true)] [string]$PslMonadRoot, [switch]$Force, [switch]$WhatIf ) begin { function MaybeTerminatingWarning { param([string]$Message) if ($Force) { Write-Warning "$Message : ignoring (-Force)" } elseif ($WhatIf) { Write-Warning "$Message : ignoring (-WhatIf)" } else { throw "$Message : use -Force to ignore" } } if (-not (Test-Path -PathType Container $PslMonadRoot)) { throw "$pslMonadRoot is not a valid folder" } # Do some intelligens to prevent shouting us in the foot with CL management # finding base-line CL $cl = git --git-dir="$PSScriptRoot/.git" tag | % {if ($_ -match 'SD.(\d+)$') {[int]$Matches[1]} } | Sort-Object -Descending | Select-Object -First 1 if ($cl) { log "Current base-line CL is SD:$cl (based on tags)" } else { MaybeTerminatingWarning "Could not determine base-line CL based on tags" } try { Push-Location $PslMonadRoot if (git status --porcelain -uno) { MaybeTerminatingWarning "$pslMonadRoot has changes" } if (git log --grep="SD:$cl" HEAD^..HEAD) { log "$pslMonadRoot HEAD matches [SD:$cl]" } else { Write-Warning "Try to checkout this commit in $pslMonadRoot :" git log --grep="SD:$cl" | Write-Warning MaybeTerminatingWarning "$pslMonadRoot HEAD doesn't match [SD:$cl]" } } finally { Pop-Location } $map = @{} } process { $map += Get-Mappings $Path -Root $PslMonadRoot } end { $map.GetEnumerator() | % { New-Item -ItemType Directory (Split-Path $_.Value) -ErrorAction SilentlyContinue > $null Copy-Item $_.Key $_.Value -Verbose:([bool]$PSBoundParameters['Verbose']) -WhatIf:$WhatIf } } } function Get-Mappings { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [string[]]$Path = "$PSScriptRoot", [string]$Root, [switch]$KeepRelativePaths ) begin { $mapFiles = @() } process { Write-Verbose "Discovering map files in $Path" $count = $mapFiles.Count if (-not (Test-Path $Path)) { throw "Mapping file not found in $mappingFilePath" } if (Test-Path -PathType Container $Path) { $mapFiles += Get-ChildItem -Recurse $Path -Filter 'map.json' -File } else { # it exists and it's a file, don't check the name pattern $mapFiles += Get-ChildItem $Path } Write-Verbose "Found $($mapFiles.Count - $count) map files in $Path" } end { $map = @{} $mapFiles | % { $rawHashtable = $_ | Get-Content -Raw | ConvertFrom-Json | Convert-PSObjectToHashtable $mapRoot = Split-Path $_.FullName if ($KeepRelativePaths) { # not very elegant way to find relative for the current directory path $mapRoot = $mapRoot.Substring($PSScriptRoot.Length + 1) # keep original unix-style paths for git $mapRoot = $mapRoot.Replace('\', '/') } $rawHashtable.GetEnumerator() | % { $newKey = if ($Root) { Join-Path $Root $_.Key } else { $_.Key } $newValue = if ($KeepRelativePaths) { ($mapRoot + '/' + $_.Value) } else { Join-Path $mapRoot $_.Value } $map[$newKey] = $newValue } } return $map } } <# .EXAMPLE Send-GitDiffToSd -diffArg1 32b90c048aa0c5bc8e67f96a98ea01c728c4a5be~1 -diffArg2 32b90c048aa0c5bc8e67f96a98ea01c728c4a5be -AdminRoot d:\e\ps_dev\admin Apply a signle commit to admin folder #> function Send-GitDiffToSd { param( [Parameter(Mandatory)] [string]$diffArg1, [Parameter(Mandatory)] [string]$diffArg2, [Parameter(Mandatory)] [string]$AdminRoot, [switch]$WhatIf ) # this is only for windows, because you cannot have SD enlistment on Linux $patchPath = (ls (Join-Path (get-command git).Source '..\..') -Recurse -Filter 'patch.exe').FullName $m = Get-Mappings -KeepRelativePaths -Root $AdminRoot $affectedFiles = git diff --name-only $diffArg1 $diffArg2 $affectedFiles | % { log "Changes in file $_" } $rev = Get-InvertedOrderedMap $m foreach ($file in $affectedFiles) { if ($rev.Contains) { $sdFilePath = $rev[$file] if (-not $sdFilePath) { Write-Warning "Cannot find mapped file for $file, skipping" continue } $diff = git diff $diffArg1 $diffArg2 -- $file if ($diff) { log "Apply patch to $sdFilePath" Set-Content -Value $diff -Path $env:TEMP\diff -Encoding Ascii if ($WhatIf) { log "Patch content" Get-Content $env:TEMP\diff } else { & $patchPath --binary -p1 $sdFilePath $env:TEMP\diff } } else { log "No changes in $file" } } else { log "Ignore changes in $file, because there is no mapping for it" } } } function Start-TypeGen { [CmdletBinding()] param() # Add .NET CLI tools to PATH Find-Dotnet Push-Location "$PSScriptRoot/src/TypeCatalogParser" try { dotnet run } finally { Pop-Location } Push-Location "$PSScriptRoot/src/TypeCatalogGen" try { dotnet run ../Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CorePsTypeCatalog.cs powershell.inc } finally { Pop-Location } } function Start-ResGen { [CmdletBinding()] param() Get-ChildItem $PSScriptRoot/src -Directory | ? { Get-ChildItem (Join-Path $_.FullName 'resources') -ErrorAction SilentlyContinue} | % { $_. Name} | % { $module = $_ Get-ChildItem "$PSScriptRoot/src/$module/resources" -Filter '*.resx' | % { $className = $_.Name.Replace('.resx', '') $xml = [xml](Get-Content -raw $_.FullName) $fileName = $className $genSource = Get-StronglyTypeCsFileForResx -xml $xml -ModuleName $module -ClassName $className $outPath = "$PSScriptRoot/src/$module/gen/$fileName.cs" Write-Verbose "ResGen for $outPath" New-Item -Type Directory -ErrorAction SilentlyContinue (Split-Path $outPath) > $null Set-Content -Encoding Ascii -Path $outPath -Value $genSource } } } function Find-Dotnet() { $originalPath = $env:PATH $dotnetPath = if ($IsWindows) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" } if (-not (precheck 'dotnet' "Could not find 'dotnet', appending $dotnetPath to PATH.")) { $env:PATH += [IO.Path]::PathSeparator + $dotnetPath } if (-not (precheck 'dotnet' "Still could not find 'dotnet', restoring PATH.")) { $env:PATH = $originalPath } } function Start-XamlGen { [CmdletBinding()] param( [Parameter()] [ValidateSet("Debug", "Release")] [string] $MSBuildConfiguration = "Release" ) Use-MSBuild Get-ChildItem -Path "$PSScriptRoot/src" -Directory | % { $XamlDir = Join-Path -Path $_.FullName -ChildPath Xamls if ((Test-Path -Path $XamlDir -PathType Container) -and (@(Get-ChildItem -Path "$XamlDir\*.xaml").Count -gt 0)) { $OutputDir = Join-Path -Path $env:TEMP -ChildPath "_Resolve_Xaml_" Remove-Item -Path $OutputDir -Recurse -Force -ErrorAction SilentlyContinue mkdir -Path $OutputDir -Force > $null # we will get failures, but it's ok: we only need to copy *.g.cs files in the dotnet cli project. $SourceDir = ConvertFrom-Xaml -Configuration $MSBuildConfiguration -OutputDir $OutputDir -XamlDir $XamlDir -IgnoreMsbuildFailure:$true $DestinationDir = Join-Path -Path $_.FullName -ChildPath gen New-Item -ItemType Directory $DestinationDir -ErrorAction SilentlyContinue > $null $filesToCopy = Get-Item "$SourceDir\*.cs", "$SourceDir\*.g.resources" if (-not $filesToCopy) { throw "No .cs or .g.resources files are generated for $XamlDir, something went wrong. Run 'Start-XamlGen -Verbose' for details." } $filesToCopy | % { $sourcePath = $_.FullName Write-Verbose "Copy generated xaml artifact: $sourcePath -> $DestinationDir" Copy-Item -Path $sourcePath -Destination $DestinationDir } } } } $Script:XamlProj = @" C# Microsoft.PowerShell.Activities library {0} Any CPU {1} true {2} False False False "@ $Script:XamlProjPage = @' '@ function script:ConvertFrom-Xaml { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] $Configuration, [Parameter(Mandatory=$true)] [string] $OutputDir, [Parameter(Mandatory=$true)] [string] $XamlDir, [switch] $IgnoreMsbuildFailure ) log "ConvertFrom-Xaml for $XamlDir" $Pages = "" Get-ChildItem -Path "$XamlDir\*.xaml" | % { $Page = $Script:XamlProjPage -f $_.FullName $Pages += $Page } $XamlProjContent = $Script:XamlProj -f $Configuration, $OutputDir, $Pages $XamlProjPath = Join-Path -Path $OutputDir -ChildPath xaml.proj Set-Content -Path $XamlProjPath -Value $XamlProjContent -Encoding Ascii -NoNewline -Force msbuild $XamlProjPath | Write-Verbose if ($LASTEXITCODE -ne 0) { $message = "When processing $XamlDir 'msbuild $XamlProjPath > `$null' failed with exit code $LASTEXITCODE" if ($IgnoreMsbuildFailure) { Write-Verbose $message } else { throw $message } } return (Join-Path -Path $OutputDir -ChildPath "obj\Any CPU\$Configuration") } function script:Use-MSBuild { # TODO: we probably should require a particular version of msbuild, if we are taking this dependency # msbuild v14 and msbuild v4 behaviors are different for XAML generation $frameworkMsBuildLocation = "${env:SystemRoot}\Microsoft.Net\Framework\v4.0.30319\msbuild" $msbuild = get-command msbuild -ErrorAction SilentlyContinue if ($msbuild) { # all good, nothing to do return } if (-not (Test-Path $frameworkMsBuildLocation)) { throw "msbuild not found in '$frameworkMsBuildLocation'. Install Visual Studio 2015." } Set-Alias msbuild $frameworkMsBuildLocation -Scope Script } function script:log([string]$message) { Write-Host -Foreground Green $message } function script:precheck([string]$command, [string]$missedMessage) { $c = Get-Command $command -ErrorAction SilentlyContinue if (-not $c) { Write-Warning $missedMessage return $false } else { return $true } } function script:Get-InvertedOrderedMap { param( $h ) $res = [ordered]@{} foreach ($q in $h.GetEnumerator()) { if ($res.Contains($q.Value)) { throw "Cannot invert hashtable: duplicated key $($q.Value)" } $res[$q.Value] = $q.Key } return $res } ## this function is from Dave Wyatt's answer on ## http://stackoverflow.com/questions/22002748/hashtables-from-convertfrom-json-have-different-type-from-powershells-built-in-h function script:Convert-PSObjectToHashtable { param ( [Parameter(ValueFromPipeline)] $InputObject ) process { if ($null -eq $InputObject) { return $null } if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { $collection = @( foreach ($object in $InputObject) { Convert-PSObjectToHashtable $object } ) Write-Output -NoEnumerate $collection } elseif ($InputObject -is [psobject]) { $hash = @{} foreach ($property in $InputObject.PSObject.Properties) { $hash[$property.Name] = Convert-PSObjectToHashtable $property.Value } $hash } else { $InputObject } } } # this function wraps native command Execution # for more information, read https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/ function script:Start-NativeExecution([scriptblock]$sb) { $backupEAP = $script:ErrorActionPreference $script:ErrorActionPreference = "Continue" try { & $sb # note, if $sb doens't have a native invokation, $LASTEXITCODE will # point to the obsolete value if ($LASTEXITCODE -ne 0) { throw "Execution of {$sb} failed with exit code $LASTEXITCODE" } } finally { $script:ErrorActionPreference = $backupEAP } } function script:Get-StronglyTypeCsFileForResx { param($xml, $ModuleName, $ClassName) # Example # # $ClassName = Full.Name.Of.The.ClassFoo # $shortClassName = ClassFoo # $namespaceName = Full.Name.Of.The $shortClassName = $ClassName $namespaceName = $null $lastIndexOfDot = $className.LastIndexOf(".") if ($lastIndexOfDot -ne -1) { $namespaceName = $className.Substring(0, $lastIndexOfDot) $shortClassName = $className.Substring($lastIndexOfDot + 1) } $banner = @' //------------------------------------------------------------------------------ // // This code was generated by a Start-ResGen funciton from build.psm1. // To add or remove a member, edit your .ResX file then rerun Start-ResGen. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ {0} '@ $namespace = @' namespace {0} {{ {1} }} '@ $body = @' using System; using System.Reflection; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class {0} {{ private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal {0}() {{ }} /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager {{ get {{ if (object.ReferenceEquals(resourceMan, null)) {{ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("{1}.resources.{3}", typeof({0}).GetTypeInfo().Assembly); resourceMan = temp; }} return resourceMan; }} }} /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture {{ get {{ return resourceCulture; }} set {{ resourceCulture = value; }} }} {2} }} '@ $entry = @' /// /// Looks up a localized string similar to {1} /// internal static string {0} {{ get {{ return ResourceManager.GetString("{0}", resourceCulture); }} }} '@ $entries = $xml.root.data | % { if ($_) { $val = $_.value.Replace("`n", "`n ///") $name = $_.name.Replace(' ', '_') $entry -f $name,$val } } | Out-String $bodyCode = $body -f $shortClassName,$ModuleName,$entries,$ClassName if ($NamespaceName) { $bodyCode = $namespace -f $NamespaceName, $bodyCode } $resultCode = $banner -f $bodyCode return $resultCode -replace "`r`n?|`n","`r`n" } # Builds coming out of this project can have version number as 'a.b.c' OR 'a.b.c-d-f' # This function converts the above version into major.minor[.build[.revision]] format function Get-PackageVersionAsMajorMinorBuildRevision { [CmdletBinding()] param ( # Version of the Package [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Version ) Write-Verbose "Extract the version in the form of major.minor[.build[.revision]] for $Version" $packageVersionTokens = $Version.Split('-') $packageVersion = ([regex]::matches($Version, "\d+(\.\d+)+"))[0].value if (1 -eq $packageVersionTokens.Count) { # In case the input is of the form a.b.c, add a '0' at the end for revision field $packageVersion = $packageVersion + '.0' } elseif (1 -lt $packageVersionTokens.Count) { # We have all the four fields $packageVersion = $packageVersion + '.' + $packageVersionTokens[1] } return $packageVersion } function New-MSIPackage { [CmdletBinding()] param ( # Name of the Product [ValidateNotNullOrEmpty()] [string] $ProductName = 'PowerShell', # Version of the Product [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductVersion, # Product Guid needs to change for every version to support SxS install [ValidateNotNullOrEmpty()] [string] $ProductGuid = 'a5249933-73a1-4b10-8a4c-13c98bdc16fe', # Source Path to the Product Files - required to package the contents into an MSI [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $ProductSourcePath, # File describing the MSI Package creation semantics [ValidateNotNullOrEmpty()] [string] $ProductWxsPath = (Join-Path $pwd '\assets\Product.wxs'), # Path to Assets folder containing artifacts such as icons, images [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AssetsPath ) $wixToolsetBinPath = "${env:ProgramFiles(x86)}\WiX Toolset v3.10\bin" Write-Verbose "Ensure Wix Toolset is present on the machine @ $wixToolsetBinPath" if (-not (Test-Path $wixToolsetBinPath)) { throw "Install Wix Toolset prior to running this script - https://wix.codeplex.com/downloads/get/1540240" } Write-Verbose "Initialize Wix executables - Heat.exe, Candle.exe, Light.exe" $wixHeatExePath = Join-Path $wixToolsetBinPath "Heat.exe" $wixCandleExePath = Join-Path $wixToolsetBinPath "Candle.exe" $wixLightExePath = Join-Path $wixToolsetBinPath "Light.exe" $ProductVersion = Get-PackageVersionAsMajorMinorBuildRevision -Version $ProductVersion -Verbose $assetsInSourcePath = "$ProductSourcePath" + '\assets' New-Item $assetsInSourcePath -type directory -Force | Write-Verbose $assetsInSourcePath = Join-Path $ProductSourcePath 'assets' Write-Verbose "Place dependencies such as icons to $assetsInSourcePath" Copy-Item "$AssetsPath\*.ico" $assetsInSourcePath -Force $productVersionWithName = $ProductName + "_" + $ProductVersion Write-Verbose "Create MSI for Product $productVersionWithName" [Environment]::SetEnvironmentVariable("ProductSourcePath", $ProductSourcePath, "Process") [Environment]::SetEnvironmentVariable("ProductName", $ProductName, "Process") [Environment]::SetEnvironmentVariable("ProductGuid", $ProductGuid, "Process") [Environment]::SetEnvironmentVariable("ProductVersion", $ProductVersion, "Process") [Environment]::SetEnvironmentVariable("ProductVersionWithName", $productVersionWithName, "Process") $wixFragmentPath = (Join-path $env:Temp "Fragment.wxs") $wixObjProductPath = (Join-path $env:Temp "Product.wixobj") $wixObjFragmentPath = (Join-path $env:Temp "Fragment.wixobj") $msiLocationPath = Join-Path $pwd "$productVersionWithName.msi" Remove-Item -ErrorAction SilentlyContinue $msiLocationPath -Force & $wixHeatExePath dir $ProductSourcePath -dr $productVersionWithName -cg $productVersionWithName -gg -sfrag -srd -scom -sreg -out $wixFragmentPath -var env.ProductSourcePath -v | Write-Verbose & $wixCandleExePath "$ProductWxsPath" "$wixFragmentPath" -out (Join-Path "$env:Temp" "\\") -arch x64 -v | Write-Verbose & $wixLightExePath -out "$productVersionWithName.msi" $wixObjProductPath $wixObjFragmentPath -ext WixUIExtension -v | Write-Verbose Remove-Item -ErrorAction SilentlyContinue *.wixpdb -Force Write-Verbose "You can find the MSI @ $msiLocationPath" return $msiLocationPath } # Function to create an Appx package compatible with Windows 8.1 and above function New-AppxPackage { [CmdletBinding()] param ( # Name of the Package [ValidateNotNullOrEmpty()] [string] $PackageName = 'PowerShell', # Version of the Package [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $PackageVersion, # Source Path to the Binplaced Files [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SourcePath, # Path to Assets folder containing Appx specific artifacts [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $AssetsPath ) $PackageVersion = Get-PackageVersionAsMajorMinorBuildRevision -Version $PackageVersion -Verbose Write-Verbose "Package Version is $PackageVersion" $win10sdkBinPath = "${env:ProgramFiles(x86)}\Windows Kits\10\bin\x64" Write-Verbose "Ensure Win10 SDK is present on the machine @ $win10sdkBinPath" if (-not (Test-Path $win10sdkBinPath)) { throw "Install Win10 SDK prior to running this script - https://go.microsoft.com/fwlink/p/?LinkID=698771" } Write-Verbose "Ensure Source Path is valid - $SourcePath" if (-not (Test-Path $SourcePath)) { throw "Invalid SourcePath - $SourcePath" } Write-Verbose "Ensure Assets Path is valid - $AssetsPath" if (-not (Test-Path $AssetsPath)) { throw "Invalid AssetsPath - $AssetsPath" } Write-Verbose "Initialize MakeAppx executable path" $makeappxExePath = Join-Path $win10sdkBinPath "MakeAppx.exe" $appxManifest = @" PowerShell Microsoft Corporation #LOGO# "@ $appxManifest = $appxManifest.Replace('#VERSION#', $PackageVersion) $appxManifest = $appxManifest.Replace('#LOGO#', 'Assets\Powershell_256.png') $appxManifest = $appxManifest.Replace('#SQUARE150x150LOGO#', 'Assets\Powershell_256.png') $appxManifest = $appxManifest.Replace('#SQUARE44x44LOGO#', 'Assets\Powershell_48.png') Write-Verbose "Place Appx Manifest in $SourcePath" $appxManifest | Out-File "$SourcePath\AppxManifest.xml" -Force $assetsInSourcePath = "$SourcePath" + '\Assets' New-Item $assetsInSourcePath -type directory -Force | Out-Null $assetsInSourcePath = Join-Path $SourcePath 'Assets' Write-Verbose "Place AppxManifest dependencies such as images to $assetsInSourcePath" Copy-Item "$AssetsPath\*.png" $assetsInSourcePath -Force $appxPackageName = $PackageName + "_" + $PackageVersion $appxPackagePath = "$pwd\$appxPackageName.appx" Write-Verbose "Calling MakeAppx from $makeappxExePath to create the package @ $appxPackagePath" & $makeappxExePath pack /o /v /d $SourcePath /p $appxPackagePath | Write-Verbose Write-Verbose "Clean-up Appx artifacts and Assets from $SourcePath" Remove-Item $assetsInSourcePath -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$SourcePath\AppxManifest.xml" -Force -ErrorAction SilentlyContinue return $appxPackagePath }