From 70ac535371effeefb315a056291369b351c65743 Mon Sep 17 00:00:00 2001
From: Wenying Dong <wenyingd@vmware.com>
Date: Thu, 30 May 2024 18:32:08 +0800
Subject: [PATCH] [Windows] Optimize the containerized OVS installation

1. Add logic to check the installed OVSext drivers, if only the desired version
   of driver is already installed, skip the installation; otherwise, remove the
   existing the drivers and re-install.
2. Add logic to check the installed VC redistributable files, if the existing
   installd vc_redist version is equal to or higher than the lowest requirement,
   skip the installation; otherwise re-install with the provided files.
3. Optimize the logic of maintaining the env paths by removing the duplicated
   paths.
4. Optimize Uninstall-OVS script by removing OVS bin paths from the system path
   after it is deleted.

Signed-off-by: Wenying Dong <wenyingd@vmware.com>
---
 .../antrea-windows/conf/Run-AntreaAgent.ps1   |   2 +-
 .../conf/ovs/Install-OVSDriver.ps1            |  41 +-
 .../antrea-windows/conf/ovs/Run-AntreaOVS.ps1 |   2 +-
 build/images/Dockerfile.build.windows         |   3 +-
 build/yamls/antrea-windows-with-ovs.yml       |  47 +-
 build/yamls/antrea-windows.yml                |   4 +-
 ci/jenkins/test.sh                            |   7 +-
 docs/windows.md                               |  26 +-
 hack/windows/Install-OVS.ps1                  | 642 +++++++++++++-----
 hack/windows/Uninstall-OVS.ps1                |  35 +-
 10 files changed, 550 insertions(+), 259 deletions(-)

diff --git a/build/charts/antrea-windows/conf/Run-AntreaAgent.ps1 b/build/charts/antrea-windows/conf/Run-AntreaAgent.ps1
index bf86ccb5c80..2e1d9a8b987 100644
--- a/build/charts/antrea-windows/conf/Run-AntreaAgent.ps1
+++ b/build/charts/antrea-windows/conf/Run-AntreaAgent.ps1
@@ -1,5 +1,5 @@
 $ErrorActionPreference = "Stop"
 $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
 $mountPath =  ($mountPath.Replace('\', '/')).TrimEnd('/')
-$env:PATH = $env:PATH + ";$mountPath/Windows/System32;$mountPath/k/antrea/bin;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
+$env:PATH = $env:PATH + ";$mountPath/k/antrea/bin;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
 & antrea-agent --config=$mountPath/etc/antrea/antrea-agent.conf --logtostderr=false --log_dir=c:/var/log/antrea --alsologtostderr --log_file_max_size=100 --log_file_max_num=4 --v=0
diff --git a/build/charts/antrea-windows/conf/ovs/Install-OVSDriver.ps1 b/build/charts/antrea-windows/conf/ovs/Install-OVSDriver.ps1
index 63dd1089e9b..f68da85394f 100644
--- a/build/charts/antrea-windows/conf/ovs/Install-OVSDriver.ps1
+++ b/build/charts/antrea-windows/conf/ovs/Install-OVSDriver.ps1
@@ -1,37 +1,14 @@
 $ErrorActionPreference = "Stop"
 $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
 $mountPath = ($mountPath.Replace('\', '/')).TrimEnd('/')
-$OVSDriverDir = "$mountPath\openvswitch\driver"
-
-# Check if OVSExt driver is already installed
-$driverStatus = netcfg -q ovsext
-if ($driverStatus -like '*not installed*') {
-  # Install OVS Driver
-  $result = netcfg -l $OVSDriverDir/ovsext.inf -c s -i OVSExt
-  if ($result -like '*failed*') {
-    Write-Host "Failed to install OVSExt driver: $result"
-    exit 1
-  }
-  Write-Host "OVSExt driver has been installed"
+$OVSInstallScript = "$mountPath\k\antrea\Install-OVS.ps1"
+if (-not (Test-Path $OVSInstallScript)) {
+  Write-Host "Installation script not found: $OVSInstallScript, you may be using an invalid antrea-windows container image"
+  exit 1
 }
-
-# Check if the VC redistributable is already installed.
-$OVSRedistDir="$mountPath\openvswitch\redist"
-if (Test-Path $OVSRedistDir) {
-  $dllFound = $false
-  $paths = $env:PATH -split ';'
-  foreach ($path in $paths) {
-    $dllFiles = Get-ChildItem -Path $path -Filter "vcruntime*.dll" -File -ErrorAction SilentlyContinue
-    if ($dllFiles.Count -gt 0) {
-      $dllFound = $true
-      break
-    }
-  }
-
-  # vcruntime dlls are not installed on the host, then install the binaries.
-  if (-not $dllFound) {
-    Get-ChildItem $OVSRedistDir -Filter *.exe | ForEach-Object {
-      Start-Process -FilePath $_.FullName -Args '/install /passive /norestart' -Verb RunAs -Wait
-    }
-  }
+& $OVSInstallScript -LocalFile "$mountPath/openvswitch" -InstallUserspace $false
+If (!$?) {
+  Write-Host "Failed to install OVS driver"
+  exit 1
 }
+Write-Host "Completed OVS driver installation"
diff --git a/build/charts/antrea-windows/conf/ovs/Run-AntreaOVS.ps1 b/build/charts/antrea-windows/conf/ovs/Run-AntreaOVS.ps1
index ee0747ada51..50032c29604 100644
--- a/build/charts/antrea-windows/conf/ovs/Run-AntreaOVS.ps1
+++ b/build/charts/antrea-windows/conf/ovs/Run-AntreaOVS.ps1
@@ -1,7 +1,7 @@
 $ErrorActionPreference = "Stop"
 $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
 $mountPath = ($mountPath.Replace('\', '/')).TrimEnd('/')
-$env:PATH = $env:PATH + ";$mountPath/Windows/System32;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
+$env:PATH = $env:PATH + ";$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
 $OVSDriverDir = "$mountPath\openvswitch\driver"
 
 # Configure OVS processes
diff --git a/build/images/Dockerfile.build.windows b/build/images/Dockerfile.build.windows
index 41c56939969..252b873c0f3 100644
--- a/build/images/Dockerfile.build.windows
+++ b/build/images/Dockerfile.build.windows
@@ -40,7 +40,8 @@ RUN --mount=type=cache,target=/go/pkg/mod/ \
 RUN mkdir -p /go/k/antrea/bin && \
     cp /antrea/bin/antrea-agent.exe /go/k/antrea/bin/ && \
     cp /antrea/bin/antctl.exe /go/k/antrea/bin/ && \
-    cp /antrea/bin/antrea-cni.exe /go/k/antrea/cni/antrea.exe
+    cp /antrea/bin/antrea-cni.exe /go/k/antrea/cni/antrea.exe && \
+    cp /antrea/hack/windows/Install-OVS.ps1 /go/k/antrea/
 
 FROM antrea/windows-ovs:${OVS_VERSION} AS antrea-ovs
 
diff --git a/build/yamls/antrea-windows-with-ovs.yml b/build/yamls/antrea-windows-with-ovs.yml
index fb4d7d69748..a3b0987f0a9 100644
--- a/build/yamls/antrea-windows-with-ovs.yml
+++ b/build/yamls/antrea-windows-with-ovs.yml
@@ -40,51 +40,28 @@ data:
     $ErrorActionPreference = "Stop"
     $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
     $mountPath =  ($mountPath.Replace('\', '/')).TrimEnd('/')
-    $env:PATH = $env:PATH + ";$mountPath/Windows/System32;$mountPath/k/antrea/bin;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
+    $env:PATH = $env:PATH + ";$mountPath/k/antrea/bin;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
     & antrea-agent --config=$mountPath/etc/antrea/antrea-agent.conf --logtostderr=false --log_dir=c:/var/log/antrea --alsologtostderr --log_file_max_size=100 --log_file_max_num=4 --v=0
   Install-OVSDriver.ps1: |
     $ErrorActionPreference = "Stop"
     $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
     $mountPath = ($mountPath.Replace('\', '/')).TrimEnd('/')
-    $OVSDriverDir = "$mountPath\openvswitch\driver"
-
-    # Check if OVSExt driver is already installed
-    $driverStatus = netcfg -q ovsext
-    if ($driverStatus -like '*not installed*') {
-      # Install OVS Driver
-      $result = netcfg -l $OVSDriverDir/ovsext.inf -c s -i OVSExt
-      if ($result -like '*failed*') {
-        Write-Host "Failed to install OVSExt driver: $result"
-        exit 1
-      }
-      Write-Host "OVSExt driver has been installed"
+    $OVSInstallScript = "$mountPath\k\antrea\Install-OVS.ps1"
+    if (-not (Test-Path $OVSInstallScript)) {
+      Write-Host "Installation script not found: $OVSInstallScript, you may be using an invalid antrea-windows container image"
+      exit 1
     }
-
-    # Check if the VC redistributable is already installed.
-    $OVSRedistDir="$mountPath\openvswitch\redist"
-    if (Test-Path $OVSRedistDir) {
-      $dllFound = $false
-      $paths = $env:PATH -split ';'
-      foreach ($path in $paths) {
-        $dllFiles = Get-ChildItem -Path $path -Filter "vcruntime*.dll" -File -ErrorAction SilentlyContinue
-        if ($dllFiles.Count -gt 0) {
-          $dllFound = $true
-          break
-        }
-      }
-
-      # vcruntime dlls are not installed on the host, then install the binaries.
-      if (-not $dllFound) {
-        Get-ChildItem $OVSRedistDir -Filter *.exe | ForEach-Object {
-          Start-Process -FilePath $_.FullName -Args '/install /passive /norestart' -Verb RunAs -Wait
-        }
-      }
+    & $OVSInstallScript -LocalFile "$mountPath/openvswitch" -InstallUserspace $false
+    If (!$?) {
+      Write-Host "Failed to install OVS driver"
+      exit 1
     }
+    Write-Host "Completed OVS driver installation"
   Run-AntreaOVS.ps1: |
     $ErrorActionPreference = "Stop"
     $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
     $mountPath = ($mountPath.Replace('\', '/')).TrimEnd('/')
-    $env:PATH = $env:PATH + ";$mountPath/Windows/System32;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
+    $env:PATH = $env:PATH + ";$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
     $OVSDriverDir = "$mountPath\openvswitch\driver"
 
     # Configure OVS processes
@@ -328,7 +305,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/agent-windows: 5efe6525007ef87c58914b37d190f84bc93b8cf081d204979dffce0859ee2da3
+        checksum/agent-windows: 86f999cb18501659a52d982f20b3df5cdf666ffd849f50ed183c366e75d01ac5
         checksum/windows-config: 10ad2be0a04b1752abc224fed0124f7b1da36efc5e7323e193eb38e11b25e798
         microsoft.com/hostprocess-inherit-user: "true"
       labels:
diff --git a/build/yamls/antrea-windows.yml b/build/yamls/antrea-windows.yml
index 447cea20b6d..00d330405b7 100644
--- a/build/yamls/antrea-windows.yml
+++ b/build/yamls/antrea-windows.yml
@@ -40,7 +40,7 @@ data:
     $ErrorActionPreference = "Stop"
     $mountPath = $env:CONTAINER_SANDBOX_MOUNT_POINT
     $mountPath =  ($mountPath.Replace('\', '/')).TrimEnd('/')
-    $env:PATH = $env:PATH + ";$mountPath/Windows/System32;$mountPath/k/antrea/bin;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
+    $env:PATH = $env:PATH + ";$mountPath/k/antrea/bin;$mountPath/openvswitch/usr/bin;$mountPath/openvswitch/usr/sbin"
     & antrea-agent --config=$mountPath/etc/antrea/antrea-agent.conf --logtostderr=false --log_dir=c:/var/log/antrea --alsologtostderr --log_file_max_size=100 --log_file_max_num=4 --v=0
 kind: ConfigMap
 metadata:
@@ -233,7 +233,7 @@ spec:
   template:
     metadata:
       annotations:
-        checksum/agent-windows: adb135c962fe85e0a2bc86a45f4b8c72d89b09a1da35bb16775e547813295679
+        checksum/agent-windows: 63f16e1fadb6b1354efda21c73702b4290400181136d4d47d4b1cd6a5f82d037
         checksum/windows-config: 10ad2be0a04b1752abc224fed0124f7b1da36efc5e7323e193eb38e11b25e798
         microsoft.com/hostprocess-inherit-user: "true"
       labels:
diff --git a/ci/jenkins/test.sh b/ci/jenkins/test.sh
index 988941848ec..8d14e49a89e 100755
--- a/ci/jenkins/test.sh
+++ b/ci/jenkins/test.sh
@@ -778,15 +778,16 @@ function run_install_windows_ovs {
     govc vm.power -on $OVS_VM_NAME || true
     echo "===== Testing VM has been reverted and powered on ====="
     IP=$(govc vm.ip $OVS_VM_NAME)
-    scp -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -T hack/windows/Install-OVS.ps1 Administrator@${IP}:
-    ssh -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -n Administrator@${IP} '/bin/bash -lc "cp Install-OVS.ps1 C:/k && powershell.exe -File C:/k/Install-OVS.ps1"'
+    scp -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -T hack/windows/Install-OVS.ps1 Administrator@${IP}:/cygdrive/c/k/
+    ssh -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -n Administrator@${IP} '/bin/bash -lc "powershell.exe -File c:/k/Install-OVS.ps1 -InstallUserspace:$true"'
 
     set +e
     RC_SERVER=$(ssh -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -n Administrator@${IP} 'powershell.exe -Command "(get-service ovsdb-server).Status -eq \"Running\""')
     RC_VSWITCHD=$(ssh -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -n Administrator@${IP} 'powershell.exe -Command "(get-service ovs-vswitchd).Status -eq \"Running\""')
+    OVSDriverStatus=$(ssh -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -n Administrator@${IP} 'netcfg.exe -q ovsext')
     set -e
 
-    if [[ $RC_SERVER != *True* || $RC_VSWITCHD != *True* ]]; then
+    if [[ $RC_SERVER != *True* || $RC_VSWITCHD != *True* || $OVSDriverStatus != *"is installed"* ]]; then
         echo "=== TEST FAILURE !!! ==="
         TEST_FAILURE=true
         ssh -o StrictHostKeyChecking=no -i ${WORKDIR}/.ssh/id_rsa -n Administrator@${IP} "tar zcf openvswitch.tar.gz -C  /cygdrive/c/openvswitch/var/log openvswitch"
diff --git a/docs/windows.md b/docs/windows.md
index 810903d2c66..fc2c4e1d585 100644
--- a/docs/windows.md
+++ b/docs/windows.md
@@ -170,12 +170,12 @@ depending on whether you are using your own [signed](https://docs.microsoft.com/
 OVS kernel driver or you want to use the test-signed driver provided by Antrea,
 you will need to invoke the `Install-OVS.ps1` script differently (or not at all).
 
-| Containerized OVS daemons? | Test-signed OVS driver? | Run this command |
-| -------------------------- | ----------------------- | ---------------- |
-| Yes                        | Yes                     | `.\Install-OVS.ps1 -InstallUserspace $false` |
-| Yes                        | No                      | N/A |
-| No                         | Yes                     | `.\Install-OVS.ps1` |
-| No                         | No                      | `.\Install-OVS.ps1 -ImportCertificate $false -Local -LocalFile <PathToOVSPackage>` |
+| Containerized OVS daemons? | Test-signed OVS driver? | Run this command                                                          |
+| -------------------------- | ----------------------- |---------------------------------------------------------------------------|
+| Yes                        | Yes                     | Not required                                                              |
+| Yes                        | No                      | Not required                                                              |
+| No                         | Yes                     | `.\Install-OVS.ps1 -InstallUserspace $true`                               |
+| No                         | No                      | `.\Install-OVS.ps1 -InstallUserspace $true -LocalFile <PathToOVSPackage>` |
 
 If you used `antrea-windows-with-ovs.yml` to create the antrea-agent
 Windows DaemonSet, then you are using "Containerized OVS daemons". For all other
@@ -193,22 +193,12 @@ Bcdedit.exe -set TESTSIGNING ON
 Restart-Computer
 ```
 
-As an example, if you are using containerized OVS
-(`antrea-windows-with-ovs.yml`), and you want to use the test-signed
-OVS kernel driver provided by Antrea (not recommended for production), you would
-run the following commands:
-
-```powershell
-curl.exe -LO https://raw.githubusercontent.com/antrea-io/antrea/main/hack/windows/Install-OVS.ps1
-.\Install-OVS.ps1 -InstallUserspace $false
-```
-
-And, if you want to run OVS as Windows native services, and you are bringing
+If you want to run OVS as Windows native services, and you are bringing
 your own OVS package with a signed OVS kernel driver, you would run:
 
 ```powershell
 curl.exe -LO https://raw.githubusercontent.com/antrea-io/antrea/main/hack/windows/Install-OVS.ps1
-.\Install-OVS.ps1 -ImportCertificate $false -Local -LocalFile <PathToOVSPackage>
+.\Install-OVS.ps1 -InstallUserspace $true -LocalFile <PathToOVSPackage>
 
 # verify that the OVS services are installed
 get-service ovsdb-server
diff --git a/hack/windows/Install-OVS.ps1 b/hack/windows/Install-OVS.ps1
index 1086fc22d81..a648582c242 100644
--- a/hack/windows/Install-OVS.ps1
+++ b/hack/windows/Install-OVS.ps1
@@ -5,22 +5,20 @@
   .PARAMETER DownloadURL
   The URL of the OpenvSwitch package to be downloaded.
 
-  .PARAMETER DownloadDir
-  The path of the directory to be used to download OpenvSwitch package. The default path is the working directory.
-
   .PARAMETER OVSInstallDir
   The target installation directory. The default path is "C:\openvswitch".
-
-  .PARAMETER CheckFileHash
-  Skips checking file hash. The default value is true.
+  If this script is run from a HostProcess Container, this path must be an absolute "host path", and no be
+  under the container's mount path.
 
   .PARAMETER LocalFile
   Specifies the path of a local OpenvSwitch package to be used for installation.
-  When the param is used, "DownloadURL" and "DownloadDir" params will be ignored.
-
-  .PARAMETER ImportCertificate
-  Specifies if a certificate file is needed for OVS package. If true, certificate
-  will be retrieved from OVSExt.sys and a package.cer file will be generated.
+  When this param is used, "DownloadURL" is ignored. LocalFile is supposed to point to a directory or to a zip
+  archive, with the following contents,
+  - driver/, a directory in which the OVSext driver files are provided, including ovsext.cat, ovsext.inf, OVSExt.sys
+  - include/, a directory in which the OVS header files are provided
+  - lib/, a directory in which the libraries required by OVS are provided,
+  - usr/, a directory in which the OVS userspace binaries and libraries are provided, e.g, usr/bin/ovs-vsctl.exe, usr/sbin/ovsdb-server.exe
+  When this param points to a directory, it must be a location different from OVSInstallDir.
 
   .PARAMETER InstallUserspace
   Specifies whether OVS userspace processes are included in the installation. If false, these processes will not 
@@ -30,64 +28,36 @@
   Specifies the path of a local SSL package to be used for installation.
 #>
 Param(
-    [parameter(Mandatory = $false)] [string] $DownloadDir,
     [parameter(Mandatory = $false)] [string] $DownloadURL,
     [parameter(Mandatory = $false)] [string] $OVSInstallDir = "C:\openvswitch",
-    [parameter(Mandatory = $false)] [bool] $CheckFileHash = $true,
     [parameter(Mandatory = $false)] [string] $LocalFile,
-    [parameter(Mandatory = $false)] [bool] $ImportCertificate = $true,
     [parameter(Mandatory = $false)] [bool] $InstallUserspace = $true,
     [parameter(Mandatory = $false)] [string] $LocalSSLFile
 )
 
+$global:ProgressPreference = "SilentlyContinue"
 $ErrorActionPreference = "Stop"
-$OVSDownloadURL = "https://downloads.antrea.io/ovs/ovs-3.0.5-antrea.1-win64.zip"
+$DefaultOVSDownloadURL = "https://downloads.antrea.io/ovs/ovs-3.0.5-antrea.1-win64.zip"
 # Use a SHA256 hash to ensure that the downloaded archive is correct.
-$OVSPublishedHash = '813a0c32067f40ce4aca9ceb7cd745a120e26906e9266d13cc8bf75b147bb6a5'
+$DefaultOVSPublishedHash = '813a0c32067f40ce4aca9ceb7cd745a120e26906e9266d13cc8bf75b147bb6a5'
+# $MininalVCRedistVersion is the minimal version required by the provided Windows OVS binary. If a higher
+# version of VC redistributable file exists on the Windows host, we can skip the installation.
+$MininalVCRedistVersion="14.12.25810"
+$DefaultVCRedistsDownloadURL = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
+
+$invocationName=$($myInvocation.MyCommand.Name)
 $WorkDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)
-$OVSDownloadDir = $WorkDir
+$InstallLog = "$WorkDir\install_ovs.log"
 $PowerShellModuleBase = "C:\Windows\System32\WindowsPowerShell\v1.0\Modules"
-
-if (!$LocalFile) {
-    $OVSZip = "$OVSDownloadDir\ovs-win64.zip"
-} else {
-    $OVSZip = $LocalFile
-    $DownloadDir = Split-Path -Path $LocalFile
-}
-
-if ($DownloadDir -ne "") {
-    $OVSDownloadDir = $DownloadDir
-}
-
-$InstallLog = "$OVSDownloadDir\install_ovs.log"
-
-if ($DownloadURL -ne "") {
-    $OVSDownloadURL = $DownloadURL
-    # For user-provided URLs, do not verify the hash for the archive.
-    $OVSPublishedHash = ""
-}
+$OVSZip=""
+$DefaultOVSRunDir = "C:\openvswitch\var\run\openvswitch"
+$tempOVSDir="$env:TEMP\openvswitch"
 
 function Log($Info) {
     $time = $(get-date -Format g)
     "$time $Info `n`r" | Tee-Object $InstallLog -Append | Write-Host
 }
 
-function CreatePath($Path){
-    if ($(Test-Path $Path)) {
-        mv $Path $($Path + "_bak")
-    }
-    mkdir -p $Path | Out-Null
-}
-
-function SetEnvVar($key, $value) {
-    [Environment]::SetEnvironmentVariable($key, $value, [EnvironmentVariableTarget]::Machine)
-}
-
-function WaitExpandFiles($Src, $Dest) {
-    Log "Extract $Src to $Dest"
-    Expand-Archive -Path $Src -DestinationPath $Dest | Out-Null
-}
-
 function ServiceExists($ServiceName) {
     If (Get-Service $ServiceName -ErrorAction SilentlyContinue) {
         return $true
@@ -95,36 +65,22 @@ function ServiceExists($ServiceName) {
     return $false
 }
 
-function CheckIfOVSInstalled() {
-    if (Test-Path -Path $OVSInstallDir) {
-        Log "$OVSInstallDir already exists, exit OVS installation."
-        exit 1
-    }
-    If (ServiceExists("ovs-vswitchd")) {
-        Log "Found existing OVS service, exit OVS installation."
-        exit 0
-    }
-}
-
 function DownloadOVS() {
-    if ($LocalFile -ne "") {
-        Log "Skipping OVS download, using local file: $LocalFile"
-        return
-    }
-
-    If (!(Test-Path $OVSDownloadDir)) {
-        mkdir -p $OVSDownloadDir
-    }
-    Log "Downloading OVS package from $OVSDownloadURL to $OVSZip"
-    curl.exe -sLo $OVSZip $OVSDownloadURL
+    param (
+        [parameter(Mandatory = $true)] [string] $localZipFile,
+        [parameter(Mandatory = $true)] [string] $downloadURL,
+        [parameter(Mandatory = $true)] [string] $desiredHash
+    )
+    Log "Downloading OVS package from $downloadURL to $localZipFile"
+    curl.exe -sLo $localZipFile $downloadURL
     If (!$?) {
-        Log "Download OVS failed, URL: $OVSDownloadURL"
+        Log "Download OVS failed, URL: $downloadURL"
         exit 1
     }
 
-    if ($CheckFileHash) {
-        $FileHash = Get-FileHash $OVSZip
-        If ($OVSPublishedHash -ne "" -And $FileHash.Hash -ne $OVSPublishedHash) {
+    if ($desiredHash -ne "invalid") {
+        $fileHash = Get-FileHash $localZipFile
+        If ($fileHash.Hash -ne $desiredHash) {
             Log "SHA256 mismatch for OVS download"
             exit 1
         }
@@ -133,51 +89,25 @@ function DownloadOVS() {
     Log "Download OVS package success."
 }
 
-function InstallOVS() {
-    # unzip OVS.
-    WaitExpandFiles $OVSZip $OVSDownloadDir
-    # Copy OVS package to target dir.
-    Log "Copying OVS package from $OVSDownloadDir\openvswitch to $OVSInstallDir"
-    mv "$OVSDownloadDir\openvswitch" $OVSInstallDir
-    if (!$LocalFile) {
-        rm $OVSZip
-    }
-    # Create log and run dir.
-    $OVS_LOG_PATH = $OVSInstallDir + "\var\log\openvswitch"
-    CreatePath $OVS_LOG_PATH
-    $OVSRunDir = $OVSInstallDir + "\var\run\openvswitch"
-    CreatePath $OVSRunDir
-    $OVSDriverDir = "$OVSInstallDir\driver"
-
-    # Install OVS driver certificate.
-    $DriverFile="$OVSDriverDir\OVSExt.sys"
-    if ($ImportCertificate) {
-        $CertificateFile = "$OVSDriverDir\package.cer"
-        if (!(Test-Path $CertificateFile)) {
-            Log "No existing OVS driver certificate found, generating a new one."
-            $ExportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert;
-            $Cert = (Get-AuthenticodeSignature $DriverFile).SignerCertificate;
-            [System.IO.File]::WriteAllBytes($CertificateFile, $Cert.Export($ExportType));
-        }
-        Log "Installing OVS driver certificate."
-        Import-Certificate -FilePath "$CertificateFile" -CertStoreLocation cert:\LocalMachine\TrustedPublisher
-        Import-Certificate -FilePath "$CertificateFile" -CertStoreLocation cert:\LocalMachine\Root
-    }
-
-    # Install Microsoft Visual C++ Redistributable Package.
-    if (Test-Path $OVSInstallDir\redist) {
-        Log "Installing Microsoft Visual C++ Redistributable Package."
-        $RedistFiles = Get-ChildItem "$OVSInstallDir\redist" -Filter *.exe
-        $RedistFiles | ForEach-Object {
-            Log "Installing $_"
-            Start-Process -FilePath $_.FullName -Args '/install /passive /norestart' -Verb RunAs -Wait
-        }
+function AddToEnvPath(){
+    param (
+        [Parameter(Mandatory = $true)] [String]$path
+    )
+    $envPaths = $env:Path -split ";" | Select-Object -Unique
+    if (-not $envPaths.Contains($path)) {
+        $envPaths += $path
     }
+    $env:Path = [system.String]::Join(";", $envPaths)
+    [Environment]::SetEnvironmentVariable("Path", $env:Path, [EnvironmentVariableTarget]::Machine)
+}
 
-    # Install powershell modules
-    if (Test-Path $OVSInstallDir\scripts) {
+function CheckAndInstallScripts {
+    param (
+        [Parameter(Mandatory = $true)] [String]$OVSScriptsPath
+    )
+    if (Test-Path $OVSScriptsPath) {
         Log "Installing powershell modules."
-        $PSModuleFiles = Get-ChildItem "$OVSInstallDir\scripts" -Filter *.psm1
+        $PSModuleFiles = Get-ChildItem "$OVSScriptsPath" -Filter *.psm1
         $PSModuleFiles | ForEach-Object {
             $PSModulePath = Join-Path -Path $PowerShellModuleBase -ChildPath $_.BaseName
             if (!(Test-Path $PSModulePath)) {
@@ -187,33 +117,267 @@ function InstallOVS() {
             }
         }
     }
+}
 
-    # Install OVS kernel driver.
-    Log "Installing OVS kernel driver"
-    $VMMSStatus = $(Get-Service vmms -ErrorAction SilentlyContinue).Status
-    if (!$VMMSStatus) {
-        $VMMSStatus = "not exist"
+function CheckAndInstallVCRedists {
+    param (
+        [Parameter(Mandatory = $true)] [String]$VCRedistPath,
+        [Parameter(Mandatory = $true)] [String]$VCRedistsVersion
+    )
+    $mininalVersion = [version]$VCRedistsVersion
+    $existingVCRedists = getInstalledVcRedists
+    foreach ($redist in $existingVCRedists) {
+        $installedVersion = [version]$redist.Version
+        # VC redists files with a higher version are installed, return.
+        if ($installedVersion -ge $mininalVersion) {
+            return
+        }
     }
-    Log "Hyper-V Virtual Machine Management service status: $VMMSStatus"
-    if ($VMMSStatus -eq "Running") {
-        cmd /c "cd $OVSDriverDir && install.cmd"
-    } else {
-        cd $OVSDriverDir ; netcfg -l .\ovsext.inf -c s -i OVSExt; cd $WorkDir
+    if (-not (Test-Path $VCRedistPath)) {
+        mkdir -p $VCRedistPath
+        curl.exe -Lo $VCRedistPath\vc_redist.x64.exe $DefaultVCRedistsDownloadURL
+    }
+    # Install the VC redistributable files.
+    Get-ChildItem $VCRedistPath -Filter *.exe | ForEach-Object {
+        Start-Process -FilePath $_.FullName -Args '/install /passive /norestart' -Verb RunAs -Wait
+    }
+}
+
+function CheckAndInstallOVSDriver {
+    param (
+        [Parameter(Mandatory = $true)]
+        [String]$OVSDriverPath
+    )
+
+    $expVersion = [version]$(Get-Item $OVSDriverPath\ovsext.sys).VersionInfo.ProductVersion
+    $ovsInstalled = $(netcfg -q ovsext) -like "*is installed*"
+    $installedDrivers = getInstalledOVSDrivers
+
+    # OVSext driver with the desired version is already installed, return
+    if ($ovsInstalled -and (@($installedDrivers).Length -eq 1) -and ($installedDrivers[0].DriverVersion -eq $expVersion)){
+        return
     }
-    if (!$?) {
-        Log "Install OVS kernel driver failed, exit"
+
+    # Uninstall the existing driver which is with a different version.
+    if ($ovsInstalled) {
+        netcfg -u ovsext
+    }
+
+    # Clean up the installed ovsext drivers packages.
+    foreach ($driver in $installedDrivers) {
+        $publishdName = $driver.PublishedName
+        pnputil.exe -d $publishdName
+    }
+
+    # Import OVSext driver certificate to TrustedPublisher and Root.
+    $DriverFile="$OVSDriverPath\ovsext.sys"
+    $CertificateFile = "$OVSDriverPath\package.cer"
+    $ExportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
+    $Cert = (Get-AuthenticodeSignature $DriverFile).SignerCertificate
+    [System.IO.File]::WriteAllBytes($CertificateFile, $Cert.Export($ExportType))
+    Import-Certificate -FilePath "$CertificateFile" -CertStoreLocation cert:\LocalMachine\TrustedPublisher
+    Import-Certificate -FilePath "$CertificateFile" -CertStoreLocation cert:\LocalMachine\Root
+
+    # Install the OVSext driver with the desired version
+    # Copy $OVSDriverPath to a host path (must not be under the container's mount path) and then install
+    # ovsext.inf using the host path. This is a workaround for error code "0x80070003" with containerd v1.7+.
+    # The error happens when Windows utitilty netcfg tries to access the container's mount path "C:/hpc/".
+    $driverStagingPath="${OVSInstallDir}\driver"
+    Remove-Item -Recurse $driverStagingPath -ErrorAction SilentlyContinue
+    mkdir -p $driverStagingPath
+    cp -r $OVSDriverPath\* $driverStagingPath\
+    $result = netcfg -l $driverStagingPath\ovsext.inf -c s -i OVSExt
+
+    if ($result -like '*failed*') {
+        Log "Failed to install OVSExt driver: $result"
         exit 1
     }
-    $OVS_BIN_PATH="$OVSInstallDir\usr\bin;$OVSInstallDir\usr\sbin"
-    $env:Path += ";$OVS_BIN_PATH"
-    SetEnvVar "Path" $env:Path
+    Log "OVSExt driver has been installed"
 }
 
-function InstallDependency() {
+function getInstalledVcRedists {
+    # Get all installed Visual C++ Redistributables installed components
+    $VcRedists = listInstalledSoftware -SoftwareLike 'Microsoft Visual C++'
+
+    # Add Architecture property to each entry
+    $VcRedists | ForEach-Object { If ( $_.Name.ToLower().Contains("x64") ) `
+        { $_ | Add-Member -NotePropertyName "Architecture" -NotePropertyValue "x64" } }
+
+    return $vcRedists
+}
+
+function listInstalledSoftware {
+    param (
+        [parameter(Mandatory = $false)] [string] $SoftwareLike
+    )
+    Begin {
+        $SoftwareOutput = @()
+        $InstalledSoftware = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*)
+    }
+    Process {
+        Try
+        {
+            if ($SoftwareLike -ne "") {
+                $nameFilter = "${SoftwareLike}*"
+                $InstalledSoftware = $InstalledSoftware |
+                        Where-Object {$_.DisplayName -like "$nameFilter"}
+            }
+
+            $SoftwareOutput = $InstalledSoftware |
+                    Select-Object -Property @{
+                        Name = 'Date Installed'
+                        Exp  = {
+                            $_.Installdate
+                        }
+                    }, @{
+                        Name = 'Version'
+                        Exp  = {
+                            $_.DisplayVersion
+                        }
+                    }, @{
+                        Name = 'Name'
+                        Exp = {
+                            $_.DisplayName
+                        }
+                    }, UninstallString
+        }
+        Catch
+        {
+            # get error record
+            [Management.Automation.ErrorRecord]$e = $_
+
+            # retrieve information about runtime error
+            $info = New-Object -TypeName PSObject -Property @{
+                Exception = $e.Exception.Message
+                Reason    = $e.CategoryInfo.Reason
+                Target    = $e.CategoryInfo.TargetName
+                Script    = $e.InvocationInfo.ScriptName
+                Line      = $e.InvocationInfo.ScriptLineNumber
+                Column    = $e.InvocationInfo.OffsetInLine
+            }
+
+            # output information. Post-process collected info, and log info (optional)
+            $info
+        }
+    }
+
+    End{
+        $SoftwareOutput | Sort-Object -Property Name
+    }
+}
+
+# getInstalledOVSDrivers lists the existing drivers on Windows host, and uses "ovsext" as a filter
+# on the "OriginalName" field of the drivers. As the output of "pnputil.exe" is not structured, the
+# function translates to structured objects first, and then applies the filter.
+#
+# A sample of the command output is like this,
+#
+# $ pnputil.exe /enum-drivers
+# Microsoft PnP Utility
+#
+# Published Name:     oem3.inf
+# Original Name:      efifw.inf
+# Provider Name:      VMware, Inc.
+# Class Name:         Firmware
+# Class GUID:         {f2e7dd72-6468-4e36-b6f1-6488f42c1b52}
+# Driver Version:     04/24/2017 1.0.0.0
+# Signer Name:        Microsoft Windows Hardware Compatibility Publisher
+#
+# Published Name:     oem5.inf
+# Original Name:      pvscsi.inf
+# Provider Name:      VMware, Inc.
+# Class Name:         Storage controllers
+# Class GUID:         {4d36e97b-e325-11ce-bfc1-08002be10318}
+# Driver Version:     04/06/2018 1.3.10.0
+# Signer Name:        Microsoft Windows Hardware Compatibility Publisher
+#
+# Published Name:     oem9.inf
+# Original Name:      vmci.inf
+# Provider Name:      VMware, Inc.
+# Class Name:         System devices
+# Class GUID:         {4d36e97d-e325-11ce-bfc1-08002be10318}
+# Driver Version:     07/11/2019 9.8.16.0
+# Signer Name:        Microsoft Windows Hardware Compatibility Publisher
+#
+function getInstalledOVSDrivers {
+    $pnputilOutput = pnputil.exe /enum-drivers
+    $drivers = @()
+    $lines = $pnputilOutput -split "`r`n"
+    $driverlines = @()
+    foreach ($line in $lines) {
+        # Ignore the title line in the output.
+        if ($line -like "*Microsoft PnP Utility*") {
+            continue
+        }
+        if ($line.Trim() -eq "") {
+            if ($driverlines.Count -gt 0) {
+                $driver = $(parseDriver $driverlines)
+                $drivers += $driver
+                $driverlines = @()
+            }
+            continue
+        }
+        $driverlines += $line
+    }
+    if ($driverlines.Count -gt 0) {
+        $driver = parseDriver $driverlines
+        $drivers += $driver
+    }
+    $drivers = $drivers | Where-Object { $_.OriginalName -like "ovsext*"}
+    return $drivers
+}
+
+function parseDriver {
+    param (
+        [String[]]$driverlines
+    )
+    $driver = [PSCustomObject]@{
+        PublishedName = $null
+        ProviderName = $null
+        ClassName = $null
+        DriverVersion = $null
+        InstalledDate = $null
+        SignerName = $null
+        ClassGUID = $null
+        OriginalName = $null
+    }
+    $driverlines | ForEach-Object {
+        if ($_ -match "Published Name\s*:\s*(.+)") {
+            $driver.PublishedName = $matches[1].Trim()
+        }
+        elseif ($_ -match "Provider Name\s*:\s*(.+)") {
+            $driver.ProviderName = $matches[1].Trim()
+        }
+        elseif ($_ -match "Class Name\s*:\s*(.+)") {
+            $driver.ClassName = $matches[1].Trim()
+        }
+        elseif ($_ -match "Driver Version\s*:\s*(.+)") {
+            $dateAndVersion = $matches[1].Trim() -split " "
+            $driver.DriverVersion = [version]$dateAndVersion[1]
+            $driver.InstalledDate = $dateAndVersion[0]
+        }
+        elseif ($_ -match "Signer Name\s*:\s*(.+)") {
+            $driver.SignerName = $matches[1].Trim()
+        }
+        elseif ($_ -match "Class GUID\s*:\s*(.+)") {
+            $driver.ClassGUID = $matches[1].Trim()
+        }
+        elseif ($_ -match "Original Name\s*:\s*(.+)") {
+            $driver.OriginalName = $matches[1].Trim()
+        }
+    }
+    return $driver
+}
+
+function InstallOpenSSLFiles {
+    param (
+        [parameter(Mandatory = $true)] [string] $destinationPaths
+    )
+
     # Check if SSL library has been installed
-    $paths = $env:Path.Split(";")
+    $paths = $destinationPaths.Split(";")
     foreach($path in $paths) {
-        if ((Test-Path "$path/ssleay32.dll" -PathType Leaf) -and (Test-Path "$path/libeay32.dll" -PathType Leaf)) {
+        if ((Test-Path "$path\ssleay32.dll" -PathType Leaf) -and (Test-Path "$path\libeay32.dll" -PathType Leaf)) {
             Log "Found existing SSL library."
             return
         }
@@ -244,49 +408,215 @@ function InstallDependency() {
         Expand-Archive $SSLZip -DestinationPath openssl
         rm $SSLZip
     }
-    cp -Force openssl/*.dll $OVSInstallDir\usr\sbin\
+    $destinationPaths -Split ";" | Foreach-Object {
+        cp -Force openssl\*.dll $_\
+    }
     rm -Recurse -Force openssl
 }
 
 function ConfigOVS() {
+    param (
+        [parameter(Mandatory = $true)] [string] $OVSLocalPath
+    )
+    # Create log dir.
+    $OVSLogDir = "${OVSInstallDir}\var\log\openvswitch"
+    if (-not (Test-Path $OVSLogDir)) {
+        mkdir -p $OVSLogDir | Out-Null
+    }
+
+    # Create OVS run dir
+    $OVSRunDir = "${OVSInstallDir}\var\run\openvswitch"
+    if (-not (Test-Path $OVSRunDir)) {
+        mkdir -p $OVSRunDir | Out-Null
+    }
+    $OVSRunDirPath = $(Get-Item -Path $OVSRunDir).FullName
+    if ($OVSRunDirPath -ne $DefaultOVSRunDir) {
+        $env:OVS_RUNDIR = $OVSRunDirPath
+        [Environment]::SetEnvironmentVariable("OVS_RUNDIR", $env:OVS_RUNDIR, [EnvironmentVariableTarget]::Machine)
+    }
+
+    $OVSUsrBinDir = $(Get-Item "$OVSLocalPath\usr\bin").FullName
     # Create ovsdb config file
-    $OVS_DB_SCHEMA_PATH = "$OVSInstallDir\usr\share\openvswitch\vswitch.ovsschema"
-    $OVS_DB_PATH = "$OVSInstallDir\etc\openvswitch\conf.db"
-    if ($(Test-Path $OVS_DB_SCHEMA_PATH) -and !$(Test-Path $OVS_DB_PATH)) {
+    $OVSDBDir = "${OVSInstallDir}\etc\openvswitch"
+    if (-not (Test-Path $OVSDBDir)) {
+        mkdir -p $OVSDBDir | Out-Null
+    }
+    $OVS_DB_PATH = "${OVSDBDir}\conf.db"
+    Remove-Item $OVS_DB_PATH -ErrorAction SilentlyContinue
+    $OVS_DB_SCHEMA_PATH = "$OVSLocalPath\usr\share\openvswitch\vswitch.ovsschema"
+    if ($(Test-Path $OVS_DB_SCHEMA_PATH)) {
         Log "Creating ovsdb file"
-        ovsdb-tool create "$OVS_DB_PATH" "$OVS_DB_SCHEMA_PATH"
+        & $OVSUsrBinDir\ovsdb-tool.exe create "$OVS_DB_PATH" "$OVS_DB_SCHEMA_PATH"
+    }
+
+    # Copy OVS userspace programs to ${OVSInstallDir}
+    $installUsrSbinDir = "${OVSInstallDir}\usr\sbin"
+    if ("$installUsrSbinDir" -ne "${OVSLocalPath}\usr\sbin") {
+        Remove-Item -Recurse $installUsrSbinDir -ErrorAction SilentlyContinue
+        mkdir -p $installUsrSbinDir
+        cp -r ${OVSLocalPath}\usr\sbin\* $installUsrSbinDir\
     }
+
     # Create and start ovsdb-server service.
+    $OVSUsrSbinPath = $(Get-Item $installUsrSbinDir).FullName
     Log "Create and start ovsdb-server service"
-    sc.exe create ovsdb-server binPath= "$OVSInstallDir\usr\sbin\ovsdb-server.exe $OVSInstallDir\etc\openvswitch\conf.db  -vfile:info --remote=punix:db.sock  --remote=ptcp:6640  --log-file  --pidfile --service" start= auto
+    sc.exe create ovsdb-server binPath= "$OVSUsrSbinPath\ovsdb-server.exe $OVS_DB_PATH  -vfile:info --remote=punix:db.sock  --remote=ptcp:6640  --log-file=$OVSLogDir\ovsdb-server.log  --pidfile --service" start= auto
     sc.exe failure ovsdb-server reset= 0 actions= restart/0/restart/0/restart/0
     Start-Service ovsdb-server
     # Create and start ovs-vswitchd service.
     Log "Create and start ovs-vswitchd service."
-    sc.exe create ovs-vswitchd binpath="$OVSInstallDir\usr\sbin\ovs-vswitchd.exe  --pidfile -vfile:info --log-file  --service" start= auto depend= "ovsdb-server"
+    sc.exe create ovs-vswitchd binpath="$OVSUsrSbinPath\ovs-vswitchd.exe  --pidfile -vfile:info --log-file=$OVSLogDir\ovs-vswitchd.log  --service" start= auto depend= "ovsdb-server"
     sc.exe failure ovs-vswitchd reset= 0 actions= restart/0/restart/0/restart/0
     Start-Service ovs-vswitchd
+
     # Set OVS version.
-    $OVS_VERSION=$(Get-Item $OVSInstallDir\driver\OVSExt.sys).VersionInfo.ProductVersion
+    $OVS_VERSION=$(Get-Item $OVSLocalPath\driver\OVSExt.sys).VersionInfo.ProductVersion
     Log "Set OVS version to: $OVS_VERSION"
-    ovs-vsctl --no-wait set Open_vSwitch . ovs_version=$OVS_VERSION
+    & $OVSUsrBinDir\ovs-vsctl.exe --no-wait set Open_vSwitch . ovs_version=$OVS_VERSION
+
+    # Add OVS usr/sbin to the environment path.
+    AddToEnvPath($installUsrSbinDir)
+
+    # Antrea Pod runs as NT AUTHORITY\SYSTEM user on Windows, antrea-ovs container writes
+    # PID and conf.db files to $OVSInstallDir on Windows Node when it is running.
+    icacls ${OVSInstallDir} /grant "NT AUTHORITY\SYSTEM:(OI)(CI)F" /T
 }
 
-Log "Installation log location: $InstallLog"
+function InstallOVSServices() {
+    param (
+        [parameter(Mandatory = $true)] [string] $OVSLocalPath
+    )
 
-CheckIfOVSInstalled
+    # Remove the existing OVS Services to avoid issues.
+    If (ServiceExists("ovs-vswitchd")) {
+        stop-service ovs-vswitchd
+        sc.exe delete ovs-vswitchd
+    }
+    if (ServiceExists("ovsdb-server")) {
+        stop-service ovsdb-server
+        sc.exe delete ovsdb-server
+    }
 
-DownloadOVS
+    # Install OVS Services and configure OVSDB.
+    ConfigOVS($OVSLocalPath)
+}
+
+function PrepareOVSLocalFiles() {
+    $OVSDownloadURL = $DefaultOVSDownloadURL
+    $desiredOVSPublishedHash = $DefaultOVSPublishedHash
+    if ($LocalFile -ne "") {
+        if (-not (Test-Path $LocalFile)){
+            Log "Path $LocalFile doesn't exist, exit"
+            exit 1
+        }
+
+        $ovsFile = Get-Item $LocalFile
+        if ($ovsFile -is [System.IO.DirectoryInfo])  {
+            return $ovsFile.FullName
+        }
+
+        # $ovsFile as a zip file is supported
+        $attributes = $ovsFile.Attributes
+        if (("$attributes" -eq "Archive") -and ($ovsFile.Extension -eq ".zip" ) ) {
+            $OVSZip = $LocalFile
+            $OVSDownloadURL = ""
+            $OVSPublishedHash = ""
+        } else {
+            Log "Unsupported local file $LocalFile; it should be a zip archive"
+            exit 1
+        }
+    } else {
+        $OVSZip = "$WorkDir\ovs-win64.zip"
+        if ($DownloadURL -ne "" -and $DownloadURL -ne "$OVSDownloadURL") {
+            $OVSDownloadURL = $DownloadURL
+            $desiredOVSPublishedHash = "invalid"
+        }
+    }
+
+    # Extract zip file to $env:TEMP\openvswitch
+    if (Test-Path -Path $tempOVSDir) {
+        rm -r $tempOVSDir
+    }
+    $removeZipFile = $false
+    if ($OVSDownloadURL -ne "") {
+        DownloadOVS -localZipFile $OVSZip -downloadURL $OVSDownloadURL -desiredHash $desiredOVSPublishedHash
+        $removeZipFile = $true
+    }
+    Expand-Archive -Path $OVSZip -DestinationPath $env:TEMP | Out-Null
+    if ($removeZipFile) {
+        rm $OVSZip
+    }
+    return $tempOVSDir
+}
 
-InstallOVS
+function CopyOVSUtilities() {
+    param (
+        [parameter(Mandatory = $true)] [string] $OVSLocalPath
+    )
+    $installUsrBinPath = "${OVSInstallDir}\usr\bin"
+    $usrBinPath="${OVSLocalPath}\usr\bin"
+    Remove-Item -Recurse $installUsrBinPath -ErrorAction SilentlyContinue
+    mkdir -p $installUsrBinPath
+    cp -r $usrBinPath\* $installUsrBinPath
+    AddToEnvPath($installUsrBinPath)
+}
 
-if ($InstallUserspace -eq $true) {
-    InstallDependency
+function InstallOVS() {
+    param (
+        [parameter(Mandatory = $true)] [string] $OVSLocalPath
+    )
+    # Install powershell modules
+    $OVSScriptsPath = "${OVSLocalPath}\scripts"
+    CheckAndInstallScripts($OVSScriptsPath)
+
+    # Install VC redistributables.
+    $OVSRedistDir="${OVSLocalPath}\redist"
+    # Check if the VC redistributable is already installed. If not installed, or the installed version
+    # is lower than $MininalVCRedistVersion, install the local provided VC redistributable files or
+    # download the file and install it.
+    CheckAndInstallVCRedists -VCRedistPath $OVSRedistDir -VCRedistsVersion $MininalVCRedistVersion
+
+    # Install OVS driver.
+    $OVSDriverDir = "${OVSLocalPath}\driver"
+    Log "Installing OVS kernel driver"
+    CheckAndInstallOVSDriver($OVSDriverDir)
+
+    # Install OpenSSL dependencies.
+    $OVSBinPaths="${OVSLocalPath}\usr\bin"
+    if ($InstallUserspace -eq $true) {
+        $OVSBinPaths="${OVSBinPaths};${OVSLocalPath}\usr\sbin"
+    }
+    InstallOpenSSLFiles "$OVSBinPaths"
+
+    # Copy OVS utilities to host path "c:/openvswitch/"
+    CopyOVSUtilities($OVSLocalPath)
+
+    if ($InstallUserspace -eq $true) {
+        InstallOVSServices($OVSLocalPath)
+    }
+}
+
+if (($LocalFile -ne "") -and ($DownloadURL -ne "")) {
+    Log "LocalFile and DownloadURL are mutually exclusive, exiting"
+    exit 1
+}
+if (($LocalFile -ne "") -and ("$LocalFile" -eq "$OVSInstallDir")) {
+    Log "LocalFile and OVSInstallDir must not be the same, exiting"
+    exit 1
+}
+Log "Installation log location: $InstallLog"
 
-    ConfigOVS
+$OVSPath = PrepareOVSLocalFiles
+# $OVSFullPath is the location of all the OVS artifacts
+# If LocalFile was provided and points to a directory, this is the path to this directory.
+# If LocalFile was provided and points to a zip archive, or if LocalFile was not provided
+# (in which case we would have downloaded a default zip archive), this is the path to
+# a temporary directory to which the archive was extracted.
+$OVSFullPath = $(Get-Item -Path $OVSPath).FullName
+InstallOVS($OVSFullPath)
+# Clean up the temp OVS directory if exists
+if (Test-Path -Path $tempOVSDir) {
+    rm -r $tempOVSDir
 }
 
-# Antrea Pod runs as NT AUTHORITY\SYSTEM user on Windows, antrea-ovs container writes
-# pid and conf.db files to $OVSInstallDir on Windows host Node during runtime.
-icacls $OVSInstallDir /grant "NT AUTHORITY\SYSTEM:(OI)(CI)F" /T
 Log "OVS Installation Complete!"
diff --git a/hack/windows/Uninstall-OVS.ps1 b/hack/windows/Uninstall-OVS.ps1
index b52f477bd51..a9f297b3ec4 100644
--- a/hack/windows/Uninstall-OVS.ps1
+++ b/hack/windows/Uninstall-OVS.ps1
@@ -5,15 +5,16 @@ Param(
 )
 
 $ErrorActionPreference = "Continue"
-$OVSBinDir = "$OVSInstallDir\usr\bin"
-$OVSDriverDir = "$OVSInstallDir\driver\"
-
+$usrBinPath="$OVSInstallDir\usr\bin"
 if ($DeleteBridges) {
-    $brList = ovs-vsctl.exe list-br
-    foreach ($br in $brList) {
-        Write-Host "Delete OVS Bridge: %s $br"
-        $cmd = "$OVSBinDir\ovs-vsctl.exe --no-wait del-br $br"
-        Invoke-Expression $cmd
+    $ovsDbSock="$OVSInstallDir\var\run\openvswitch\db.sock"
+    if ((Test-Path $usrBinPath) -and (Test-Path $ovsDbSock)){
+        $env:Path="$env:Path;$usrBinPath"
+        $brList = ovs-vsctl.exe list-br
+        foreach ($br in $brList) {
+            Write-Host "Delete OVS Bridge: %s $br"
+            ovs-vsctl.exe --no-wait del-br $br
+        }
     }
 }
 
@@ -37,14 +38,17 @@ if (Get-Service ovsdb-server -ErrorAction SilentlyContinue) {
     }
 }
 # Uninstall OVS kernel driver
-cmd /c "cd $OVSDriverDir && uninstall.cmd"
+$ovsInstalled = $(netcfg -q ovsext) -like "*is installed*"
+if ($ovsInstalled) {
+    netcfg -u ovsext
+}
 if (!$?) {
     Write-Host "Failed to uninstall OVS kernel driver."
     exit 1
 }
 
 # Remove OVS installation dir
-if ($RemoveDir) {
+if ($RemoveDir -and (Test-Path $OVSInstallDir)) {
     Remove-Item -Recurse $OVSInstallDir
     if (!$?) {
         Write-Host "Failed to remove OVS dir: $OVSInstallDir."
@@ -52,4 +56,15 @@ if ($RemoveDir) {
     }
 }
 
+# Remove OVS bin paths from environment variables.
+$envPaths = @()
+$usrSbinPath="$OVSInstallDir\usr\sbin"
+foreach ($item in $($env:Path -split ";" | Select-Object -Unique)) {
+    if (($item -ne "$usrBinPath") -and ($item -ne "$usrSbinPath")) {
+        $envPaths += $item
+    }
+}
+$env:Path =$envPaths -join ";"
+[Environment]::SetEnvironmentVariable("Path", $env:Path, [EnvironmentVariableTarget]::Machine)
+
 Write-Host "Uninstall OVS success."