diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/Dotnet.Cli.Msi.Tests.csproj b/packaging/windows/Dotnet.Cli.Msi.Tests/Dotnet.Cli.Msi.Tests.csproj
new file mode 100644
index 000000000..607b1fb94
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/Dotnet.Cli.Msi.Tests.csproj
@@ -0,0 +1,63 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {16614B7F-5CA3-45AE-95C2-003AB39CC09F}
+ Library
+ Properties
+ Dotnet.Cli.Msi.Tests
+ Dotnet.Cli.Msi.Tests
+ v4.6
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/Dotnet.Cli.Msi.Tests.nuget.props b/packaging/windows/Dotnet.Cli.Msi.Tests/Dotnet.Cli.Msi.Tests.nuget.props
new file mode 100644
index 000000000..e3e9841b3
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/Dotnet.Cli.Msi.Tests.nuget.props
@@ -0,0 +1,9 @@
+
+
+
+ C:\Users\sridhper\.nuget\packages\
+
+
+
+
+
\ No newline at end of file
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/InstallFixture.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/InstallFixture.cs
new file mode 100644
index 000000000..96b599ca2
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/InstallFixture.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ public class InstallFixture : IDisposable
+ {
+ private MsiManager _msiMgr = null;
+
+ // all the tests assume that the msi to be tested is available via environment variable %CLI_MSI%
+
+ public InstallFixture()
+ {
+ string msiFile = Environment.GetEnvironmentVariable("CLI_MSI");
+
+ _msiMgr = new MsiManager(msiFile);
+
+ // make sure that the msi is not already installed, if so the machine is in a bad state
+ Assert.False(_msiMgr.IsInstalled, "The dotnet CLI msi is already installed");
+
+ _msiMgr.Install(InstallLocation);
+ Assert.True(_msiMgr.IsInstalled);
+ }
+
+ public MsiManager MsiManager
+ {
+ get
+ {
+ return _msiMgr;
+ }
+ }
+
+ public string InstallLocation
+ {
+ get
+ {
+ return Environment.ExpandEnvironmentVariables(@"%SystemDrive%\dotnet\");
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_msiMgr.IsInstalled)
+ {
+ return;
+ }
+
+ _msiMgr.UnInstall();
+ Assert.False(_msiMgr.IsInstalled, "Unable to cleanup by uninstalling dotnet");
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/InstallationTests.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/InstallationTests.cs
new file mode 100644
index 000000000..893ee1fc3
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/InstallationTests.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Xunit;
+
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ public class InstallationTests : IDisposable
+ {
+ private string _msiFile;
+ private MsiManager _msiMgr;
+
+ public InstallationTests()
+ {
+ // all the tests assume that the msi to be tested is available via environment variable %CLI_MSI%
+ _msiFile = Environment.GetEnvironmentVariable("CLI_MSI");
+ if(string.IsNullOrEmpty(_msiFile))
+ {
+ throw new InvalidOperationException("%CLI_MSI% must point to the msi that is to be tested");
+ }
+
+ _msiMgr = new MsiManager(_msiFile);
+ }
+
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(@"%SystemDrive%\dotnet")]
+ public void InstallTest(string installLocation)
+ {
+ installLocation = Environment.ExpandEnvironmentVariables(installLocation);
+ string expectedInstallLocation = string.IsNullOrEmpty(installLocation) ?
+ Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\dotnet") :
+ installLocation;
+
+ // make sure that the msi is not already installed, if so the machine is in a bad state
+ Assert.False(_msiMgr.IsInstalled, "The dotnet CLI msi is already installed");
+ Assert.False(Directory.Exists(expectedInstallLocation));
+
+ _msiMgr.Install(installLocation);
+ Assert.True(_msiMgr.IsInstalled);
+ Assert.True(Directory.Exists(expectedInstallLocation));
+
+ _msiMgr.UnInstall();
+ Assert.False(_msiMgr.IsInstalled);
+ Assert.False(Directory.Exists(expectedInstallLocation));
+ }
+
+ public void Dispose()
+ {
+ if (!_msiMgr.IsInstalled)
+ {
+ return;
+ }
+
+ _msiMgr.UnInstall();
+ Assert.False(_msiMgr.IsInstalled, "Unable to cleanup by uninstalling dotnet");
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/MsiManager.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/MsiManager.cs
new file mode 100644
index 000000000..cd56d6f08
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/MsiManager.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Deployment.WindowsInstaller;
+using Microsoft.Deployment.WindowsInstaller.Package;
+
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ public class MsiManager
+ {
+ private string _msiFile;
+ private string _productCode;
+ private InstallPackage _installPackage;
+
+ public ProductInstallation Installation
+ {
+ get
+ {
+ return ProductInstallation.AllProducts.SingleOrDefault(p => p.ProductCode == _productCode);
+ }
+ }
+
+ public string InstallLocation
+ {
+ get
+ {
+ return IsInstalled ? Installation.InstallLocation : null;
+ }
+ }
+
+ public bool IsInstalled
+ {
+ get
+ {
+ var prodInstall = Installation;
+ return Installation == null ? false : prodInstall.IsInstalled;
+ }
+ }
+
+ public string UpgradeCode
+ {
+ get
+ {
+ return _installPackage.Property["UpgradeCode"];
+ }
+ }
+
+ public MsiManager(string msiFile)
+ {
+ _msiFile = msiFile;
+
+ var ispackage = Installer.VerifyPackage(msiFile);
+ if (!ispackage)
+ {
+ throw new ArgumentException("Not a valid MSI file", msiFile);
+ }
+
+ _installPackage = new InstallPackage(msiFile, DatabaseOpenMode.ReadOnly);
+ _productCode = _installPackage.Property["ProductCode"];
+ }
+
+ public bool Install(string customLocation = null)
+ {
+ string dotnetHome = "";
+ if (!string.IsNullOrEmpty(customLocation))
+ {
+ dotnetHome = $"DOTNETHOME={customLocation}";
+ }
+ Installer.SetInternalUI(InstallUIOptions.Silent);
+ Installer.InstallProduct(_msiFile, $"ACTION=INSTALL ALLUSERS=2 ACCEPTEULA=1 {dotnetHome}");
+
+ return IsInstalled;
+ }
+
+ public bool UnInstall()
+ {
+ if (!IsInstalled)
+ {
+ throw new InvalidOperationException($"UnInstall Error: Msi at {_msiFile} is not installed.");
+ }
+
+ Installer.SetInternalUI(InstallUIOptions.Silent);
+ Installer.InstallProduct(_msiFile, "REMOVE=ALL");
+
+ return !IsInstalled;
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/PostInstallTests.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/PostInstallTests.cs
new file mode 100644
index 000000000..1f25e23c2
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/PostInstallTests.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Win32;
+using Xunit;
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ public class PostInstallTests : IClassFixture
+ {
+ InstallFixture _fixture;
+ MsiManager _msiMgr;
+
+ public PostInstallTests(InstallFixture fixture)
+ {
+ _fixture = fixture;
+ _msiMgr = fixture.MsiManager;
+ }
+
+ [Fact]
+ public void DotnetOnPathTest()
+ {
+ Assert.True(_msiMgr.IsInstalled);
+
+ Assert.True(Utils.ExistsOnPath("dotnet.exe"), "After installation dotnet tools must be on path");
+ }
+
+ [Fact]
+ public void Dotnetx64RegKeysTest()
+ {
+ var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
+ CheckRegKeys(hklm);
+ }
+
+ [Fact]
+ public void Dotnetx86RegKeysTest()
+ {
+ var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
+ CheckRegKeys(hklm);
+ }
+
+ private void CheckRegKeys(RegistryKey rootKey)
+ {
+ var regKey = rootKey.OpenSubKey(@"SOFTWARE\dotnet\Setup", false);
+
+ Assert.NotNull(regKey);
+ Assert.Equal(1, regKey.GetValue("Install"));
+ Assert.Equal(_fixture.InstallLocation, regKey.GetValue("InstallDir"));
+ Assert.NotNull(regKey.GetValue("Version"));
+ }
+
+ [Fact]
+ public void UpgradeCodeTest()
+ {
+ // magic number found in https://github.com/dotnet/cli/blob/master/packaging/windows/variables.wxi
+ Assert.Equal("{7D73E4F7-71E2-4236-8CF5-1C499BA3FF50}", _msiMgr.UpgradeCode);
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/PostUninstallTests.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/PostUninstallTests.cs
new file mode 100644
index 000000000..7b7552159
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/PostUninstallTests.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Win32;
+using Xunit;
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ public class PostUninstallTests : InstallFixture
+ {
+ private MsiManager _msiMgr;
+
+ public PostUninstallTests()
+ {
+ _msiMgr = base.MsiManager;
+ }
+
+ [Fact]
+ public void DotnetOnPathTest()
+ {
+ Assert.True(_msiMgr.IsInstalled);
+
+ _msiMgr.UnInstall();
+
+ Assert.False(_msiMgr.IsInstalled);
+ Assert.False(Utils.ExistsOnPath("dotnet.exe"), "After uninstallation dotnet tools must not be on path");
+ }
+
+ [Fact]
+ public void DotnetRegKeysTest()
+ {
+ Assert.True(_msiMgr.IsInstalled);
+
+ _msiMgr.UnInstall();
+
+ Assert.False(_msiMgr.IsInstalled);
+
+ var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
+ Assert.Null(hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup", false));
+
+ hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
+ Assert.Null(hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup", false));
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/Program.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/Program.cs
new file mode 100644
index 000000000..1404a287b
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/Program.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ class Program
+ {
+ // A main method is currently required because of https://github.com/dotnet/cli/issues/314
+ public static void Main(string[] args)
+ {
+ return;
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/Properties/AssemblyInfo.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..84f066e3d
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Dotnet.Cli.Msi.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Dotnet.Cli.Msi.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("16614b7f-5ca3-45ae-95c2-003ab39cc09f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/Utils.cs b/packaging/windows/Dotnet.Cli.Msi.Tests/Utils.cs
new file mode 100644
index 000000000..32707bd85
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/Utils.cs
@@ -0,0 +1,27 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Dotnet.Cli.Msi.Tests
+{
+ class Utils
+ {
+ internal static bool ExistsOnPath(string fileName)
+ {
+ var paths = GetCurrentPathEnvironmentVariable();
+ return paths
+ .Split(';')
+ .Any(path => File.Exists(Path.Combine(path, fileName)));
+ }
+
+ internal static string GetCurrentPathEnvironmentVariable()
+ {
+ var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
+ var regKey = hklm.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", false);
+
+ return (string)regKey.GetValue("Path");
+ }
+ }
+}
diff --git a/packaging/windows/Dotnet.Cli.Msi.Tests/project.json b/packaging/windows/Dotnet.Cli.Msi.Tests/project.json
new file mode 100644
index 000000000..51c53e92a
--- /dev/null
+++ b/packaging/windows/Dotnet.Cli.Msi.Tests/project.json
@@ -0,0 +1,17 @@
+{
+ "compilationOptions": {
+ "emitEntryPoint": true
+ },
+ "dependencies": {
+ "xunit": "2.1.0",
+ "xunit.runner.console": "2.1.0",
+ "Microsoft.Deployment.WindowsInstaller": "1.0.0"
+ },
+ "frameworks": {
+ "net46": {
+ "frameworkAssemblies": {
+ "System.Runtime": ""
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packaging/windows/generatemsi.ps1 b/packaging/windows/generatemsi.ps1
index b0a2bef2d..22201b638 100644
--- a/packaging/windows/generatemsi.ps1
+++ b/packaging/windows/generatemsi.ps1
@@ -148,7 +148,15 @@ if(!(Test-Path $DotnetMSIOutput))
return -1
}
-Write-Host -ForegroundColor Green "Successfully create dotnet MSI - $DotnetMSIOutput"
+Write-Host -ForegroundColor Green "Successfully created dotnet MSI - $DotnetMSIOutput"
+
+& $PSScriptRoot\testmsi.ps1 -inputMsi $DotnetMSIOutput
+
+if($LastExitCode -ne 0)
+{
+ Write-Host -ForegroundColor Red "Msi testing failed."
+ Exit 1
+}
$PublishScript = Join-Path $PSScriptRoot "..\..\scripts\publish\publish.ps1"
& $PublishScript -file $DotnetMSIOutput
diff --git a/packaging/windows/testmsi.ps1 b/packaging/windows/testmsi.ps1
new file mode 100644
index 000000000..8744d903a
--- /dev/null
+++ b/packaging/windows/testmsi.ps1
@@ -0,0 +1,71 @@
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+param(
+ [string]$inputMsi = $(throw "Specify the full path to the msi which needs to be tested")
+)
+
+. "$PSScriptRoot\..\..\scripts\_common.ps1"
+
+function Test-Administrator
+{
+ $user = [Security.Principal.WindowsIdentity]::GetCurrent();
+ (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
+}
+
+Write-Host "Running tests for MSI installer at $inputMsi.."
+
+if(!(Test-Path $inputMsi))
+{
+ throw "$inputMsi not found"
+}
+
+$env:CLI_MSI=$inputMsi
+$testBin="$RepoRoot\artifacts\tests\Dotnet.Cli.Msi.Tests"
+$xunitRunner="$env:USERPROFILE\.dnx\packages\xunit.runner.console\2.1.0\tools\xunit.console.exe"
+
+pushd "$Stage2Dir\bin"
+
+try {
+ .\dotnet restore `
+ --runtime win-anycpu `
+ $RepoRoot\packaging\windows\Dotnet.Cli.Msi.Tests\project.json `
+ -f https://www.myget.org/F/dotnet-buildtools/api/v3/index.json | Out-Host
+
+ if($LastExitCode -ne 0)
+ {
+ throw "dotnet restore failed with exit code $LastExitCode."
+ }
+
+ .\dotnet publish `
+ --framework net46 `
+ --runtime win-anycpu `
+ --output $testBin `
+ $RepoRoot\packaging\windows\Dotnet.Cli.Msi.Tests\project.json | Out-Host
+
+ if($LastExitCode -ne 0)
+ {
+ throw "dotnet publish failed with exit code $LastExitCode."
+ }
+<#
+ if(-Not (Test-Administrator))
+ {
+ Write-Host -ForegroundColor Yellow "Current script testmsi.ps1 is not run as admin."
+ Write-Host -ForegroundColor Yellow "Executing MSI tests require admin privileges."
+ Write-Host -ForegroundColor Yellow "Failing silently."
+ Exit 0
+ }
+
+ & $xunitRunner $testBin\Dotnet.Cli.Msi.Tests.exe | Out-Host
+
+ if($LastExitCode -ne 0)
+ {
+ throw "xunit runner failed with exit code $LastExitCode."
+ }
+#>
+}
+finally {
+ popd
+}
+
+Exit 0