diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml
index 01aeca45..0a177860 100644
--- a/.github/workflows/build-images.yml
+++ b/.github/workflows/build-images.yml
@@ -9,7 +9,7 @@ jobs:
   build-landscape2-image:
     if: github.ref == 'refs/heads/main'
     runs-on:
-      labels: ubuntu-latest-16-cores
+      labels: ubuntu-latest-8-cores
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4f762dd0..5a484280 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ on:
 jobs:
   lint-cli-tool:
     runs-on:
-      labels: ubuntu-latest-16-cores
+      labels: ubuntu-latest-8-cores
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
@@ -33,7 +33,7 @@ jobs:
 
   test-cli-tool:
     runs-on:
-      labels: ubuntu-latest-16-cores
+      labels: ubuntu-latest-8-cores
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
@@ -50,7 +50,7 @@ jobs:
 
   lint-and-test-webapp:
     runs-on:
-      labels: ubuntu-latest-16-cores
+      labels: ubuntu-latest-8-cores
     env:
       NODE_OPTIONS: --max_old_space_size=4096
     steps:
diff --git a/Cargo.toml b/Cargo.toml
index a5157852..008fd012 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,10 @@ rust-version = "1.70"
 repository = "https://github.com/cncf/landscape2"
 authors = ["Sergio Castaño Arteaga", "Cintia Sanchez Garcia"]
 
+[package.metadata.wix]
+upgrade-guid = "CFDCA2AA-9CB2-49B1-98DD-9BE9BC4DC28F"
+path-guid = "59B0489F-30BD-45E2-B29C-C87F970B4138"
+
 [dependencies]
 anyhow = "1.0.75"
 askama = { version = "0.12.1", features = ["serde-json"] }
@@ -76,11 +80,11 @@ cargo-dist-version = "0.4.0"
 # CI backends to support
 ci = ["github"]
 # The installers to generate for each app
-installers = ["shell", "homebrew"]
+installers = ["shell", "powershell", "homebrew", "msi"]
 # A GitHub repo to push Homebrew formulas to
 tap = "cncf/homebrew-landscape2"
 # Target platforms to build apps for (Rust target-triple syntax)
-targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"]
+targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
 # Publish jobs to run in CI
 publish-jobs = ["homebrew"]
 # Publish jobs to run in CI
diff --git a/build.rs b/build.rs
index f69fc3f1..2acce917 100644
--- a/build.rs
+++ b/build.rs
@@ -1,5 +1,5 @@
 use anyhow::{format_err, Result};
-use std::process::{Command, Output};
+use std::process::Command;
 use which::which;
 
 fn main() -> Result<()> {
@@ -16,21 +16,32 @@ fn main() -> Result<()> {
     }
 
     // Build web application
-    let error = |cmd: &str, output: Output| {
-        Err(format_err!(
-            "\n\n> {cmd} (stderr)\n{}\n> {cmd} (stdout)\n{}\n",
-            String::from_utf8(output.stderr)?,
-            String::from_utf8(output.stdout)?
-        ))
-    };
-    let output = Command::new("yarn").args(["--cwd", "web", "install"]).output()?;
-    if !output.status.success() {
-        return error("yarn install", output);
+    yarn(&["--cwd", "web", "install"])?;
+    yarn(&["--cwd", "web", "build"])?;
+
+    Ok(())
+}
+
+/// Run yarn command with the provided arguments.
+fn yarn(args: &[&str]) -> Result<()> {
+    // Setup command based on the target OS
+    let mut cmd;
+    if cfg!(target_os = "windows") {
+        cmd = Command::new("cmd");
+        cmd.args(["/C", "yarn"]);
+    } else {
+        cmd = Command::new("yarn");
     }
-    let output = Command::new("yarn").args(["--cwd", "web", "build"]).output()?;
+    cmd.args(args);
+
+    // Run command and check output
+    let output = cmd.output()?;
     if !output.status.success() {
-        return error("yarn build", output);
+        return Err(format_err!(
+            "\n\n> {cmd:?} (stderr)\n{}\n> {cmd:?} (stdout)\n{}\n",
+            String::from_utf8(output.stderr)?,
+            String::from_utf8(output.stdout)?
+        ));
     }
-
     Ok(())
 }
diff --git a/wix/main.wxs b/wix/main.wxs
new file mode 100644
index 00000000..1c4b7acd
--- /dev/null
+++ b/wix/main.wxs
@@ -0,0 +1,215 @@
+<?xml version='1.0' encoding='windows-1252'?>
+<!--
+  Copyright (C) 2017 Christopher R. Field.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<!--
+  The "cargo wix" subcommand provides a variety of predefined variables available
+  for customization of this template. The values for each variable are set at
+  installer creation time. The following variables are available:
+
+  TargetTriple      = The rustc target triple name.
+  TargetEnv         = The rustc target environment. This is typically either
+                      "msvc" or "gnu" depending on the toolchain downloaded and
+                      installed.
+  TargetVendor      = The rustc target vendor. This is typically "pc", but Rust
+                      does support other vendors, like "uwp".
+  CargoTargetBinDir = The complete path to the directory containing the
+                      binaries (exes) to include. The default would be
+                      "target\release\". If an explicit rustc target triple is
+                      used, i.e. cross-compiling, then the default path would
+                      be "target\<CARGO_TARGET>\<CARGO_PROFILE>",
+                      where "<CARGO_TARGET>" is replaced with the "CargoTarget"
+                      variable value and "<CARGO_PROFILE>" is replaced with the
+                      value from the "CargoProfile" variable. This can also
+                      be overriden manually with tne "target-bin-dir" flag.
+  CargoTargetDir    = The path to the directory for the build artifacts, i.e.
+                      "target".
+  CargoProfile      = The cargo profile used to build the binaries
+                      (usually "debug" or "release").
+  Version           = The version for the installer. The default is the
+                      "Major.Minor.Fix" semantic versioning number of the Rust
+                      package.
+-->
+
+<!--
+  Please do not remove these pre-processor If-Else blocks. These are used with
+  the `cargo wix` subcommand to automatically determine the installation
+  destination for 32-bit versus 64-bit installers. Removal of these lines will
+  cause installation errors.
+-->
+<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
+    <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
+<?else ?>
+    <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
+<?endif ?>
+
+<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
+
+    <Product
+        Id='*'
+        Name='landscape2'
+        UpgradeCode='CFDCA2AA-9CB2-49B1-98DD-9BE9BC4DC28F'
+        Manufacturer='Sergio Castaño Arteaga; Cintia Sanchez Garcia'
+        Language='1033'
+        Codepage='1252'
+        Version='$(var.Version)'>
+
+        <Package Id='*'
+            Keywords='Installer'
+            Description='Landscape2 CLI tool'
+            Manufacturer='Sergio Castaño Arteaga; Cintia Sanchez Garcia'
+            InstallerVersion='450'
+            Languages='1033'
+            Compressed='yes'
+            InstallScope='perMachine'
+            SummaryCodepage='1252'
+            />
+
+        <MajorUpgrade
+            Schedule='afterInstallInitialize'
+            DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
+
+        <Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
+        <Property Id='DiskPrompt' Value='landscape2 Installation'/>
+
+        <Directory Id='TARGETDIR' Name='SourceDir'>
+            <Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
+                <Directory Id='APPLICATIONFOLDER' Name='landscape2'>
+                    <!--
+                      Disabling the license sidecar file in the installer is a two step process:
+
+                      1. Comment out or remove the `Component` tag along with its contents.
+                      2. Comment out or remove the `ComponentRef` tag with the "License" Id
+                         attribute value further down in this file.
+                    -->
+                    <Component Id='License' Guid='*'>
+                        <File Id='LicenseFile'
+                            Name='License'
+                            DiskId='1'
+                            Source='License.rtf'
+                            KeyPath='yes'/>
+                    </Component>
+                    
+                    <Directory Id='Bin' Name='bin'>
+                        <Component Id='Path' Guid='59B0489F-30BD-45E2-B29C-C87F970B4138' KeyPath='yes'>
+                            <Environment
+                                Id='PATH'
+                                Name='PATH'
+                                Value='[Bin]'
+                                Permanent='no'
+                                Part='last'
+                                Action='set'
+                                System='yes'/>
+                        </Component>
+                        <Component Id='binary0' Guid='*'>
+                            <File
+                                Id='exe0'
+                                Name='landscape2.exe'
+                                DiskId='1'
+                                Source='$(var.CargoTargetBinDir)\landscape2.exe'
+                                KeyPath='yes'/>
+                        </Component>
+                    </Directory>
+                </Directory>
+            </Directory>
+        </Directory>
+
+        <Feature
+            Id='Binaries'
+            Title='Application'
+            Description='Installs all binaries and the license.'
+            Level='1'
+            ConfigurableDirectory='APPLICATIONFOLDER'
+            AllowAdvertise='no'
+            Display='expand'
+            Absent='disallow'>
+            <!--
+              Comment out or remove the following `ComponentRef` tag to remove
+              the license sidecar file from the installer.
+            -->
+            <ComponentRef Id='License'/>
+            
+            <ComponentRef Id='binary0'/>
+
+            <Feature
+                Id='Environment'
+                Title='PATH Environment Variable'
+                Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.'
+                Level='1'
+                Absent='allow'>
+                <ComponentRef Id='Path'/>
+            </Feature>
+        </Feature>
+
+        <SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
+
+        
+        <!--
+          Uncomment the following `Icon` and `Property` tags to change the product icon.
+
+          The product icon is the graphic that appears in the Add/Remove
+          Programs control panel for the application.
+        -->
+        <!--<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>-->
+        <!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->
+
+        <Property Id='ARPHELPLINK' Value='https://github.com/cncf/landscape2'/>
+        
+        <UI>
+            <UIRef Id='WixUI_FeatureTree'/>
+            <!--
+              Disabling the EULA dialog in the installer is a two step process:
+
+                 1. Uncomment the following two `Publish` tags
+                 2. Comment out or remove the `<WiXVariable Id='WixUILicenseRtf'...` tag further down
+
+            -->
+            <!--<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>-->
+            <!--<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>-->
+            
+        </UI>
+
+        <!--
+          Disabling the EULA dialog in the installer requires commenting out
+          or removing the following `WixVariable` tag
+        -->
+        <WixVariable Id='WixUILicenseRtf' Value='License.rtf'/>
+        
+        
+        <!--
+          Uncomment the next `WixVariable` tag to customize the installer's
+          Graphical User Interface (GUI) and add a custom banner image across
+          the top of each screen. See the WiX Toolset documentation for details
+          about customization.
+
+          The banner BMP dimensions are 493 x 58 pixels.
+        -->
+        <!--<WixVariable Id='WixUIBannerBmp' Value='wix\Banner.bmp'/>-->
+
+        
+        <!--
+          Uncomment the next `WixVariable` tag to customize the installer's
+          Graphical User Interface (GUI) and add a custom image to the first
+          dialog, or screen. See the WiX Toolset documentation for details about
+          customization.
+
+          The dialog BMP dimensions are 493 x 312 pixels.
+        -->
+        <!--<WixVariable Id='WixUIDialogBmp' Value='wix\Dialog.bmp'/>-->
+
+    </Product>
+
+</Wix>