PowerShell – Script to install configure and uninstall FortiClient VPN

Just another Tech site

PowerShell – Script to install configure and uninstall FortiClient VPN

Below is a single, self‑contained PowerShell 5.1 script that:

  • Installs FortiClient VPN (MSI or extracted MSI from the online EXE) silently
  • Creates an SSL‑VPN profile with a gateway and port (writes the FortiClient.conf profile)
  • Verifies installation and logs actions
  • Uninstalls FortiClient VPN cleanly by locating the MSI product and running msiexec uninstall

Use it as an admin on the target machine. The script is idempotent and includes logging. I also include short usage examples after the script.

Notes

  • Fortinet’s VPN‑only installer can be run silently; the MSI can be used with msiexec flags for unattended install.
  • FortiClient VPN profiles are commonly deployed by writing the FortiClient.conf under C:\ProgramData\Fortinet\FortiClient\vpn\. The script writes that file.

 

<#
.SYNOPSIS
  Install, configure (gateway + port), verify, and uninstall FortiClient VPN (PowerShell 5.1).

.DESCRIPTION
  Single turnkey script. Set parameters below and run elevated.
  - Installs from MSI or extracts MSI from EXE if needed.
  - Writes an SSL-VPN profile to FortiClient.conf.
  - Can uninstall by product GUID lookup.

.NOTES
  Tested for PowerShell 5.1. Run as Administrator.
#>

#region Parameters - edit these before running
# Path to installer. Can be an MSI or the FortiClientVPN online EXE (script will try to extract MSI).
$InstallerPath = "C:\Temp\FortiClientVPN.exe"

# VPN profile settings
$ProfileName = "MyVPN"
$GatewayHost = "vpn.example.com"
$GatewayPort = 443

# Action: "install" or "uninstall"
$Action = "install"

# Logging
$LogFolder = "C:\Temp\FortiClientDeploy"
$VerbosePreference = "Continue"
#endregion

# Ensure log folder
if (!(Test-Path $LogFolder)) { New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null }
$LogFile = Join-Path $LogFolder "FortiClientDeploy-$(Get-Date -Format yyyyMMdd-HHmmss).log"

function Log {
    param([string]$Message)
    $ts = (Get-Date).ToString("s")
    "$ts`t$Message" | Tee-Object -FilePath $LogFile -Append
}

function Extract-MsiFromExe {
    param([string]$ExePath, [string]$OutFolder)
    Log "Attempting to extract MSI from EXE: $ExePath"
    if (!(Test-Path $ExePath)) { throw "Installer not found: $ExePath" }
    if (!(Test-Path $OutFolder)) { New-Item -Path $OutFolder -ItemType Directory -Force | Out-Null }

    # Many FortiClient online installers extract to %LocalAppData%\Temp or create a cache folder.
    # We'll run the EXE with /quiet to force extraction then search temp for an MSI.
    $proc = Start-Process -FilePath $ExePath -ArgumentList "/quiet" -PassThru -Wait -WindowStyle Hidden
    Start-Sleep -Seconds 3

    # Search common extraction locations for FortiClient*.msi
    $candidates = @()
    $candidates += Get-ChildItem -Path "$env:LOCALAPPDATA\Temp" -Recurse -ErrorAction SilentlyContinue |
                   Where-Object { $_.Name -match "FortiClient.*\.msi$" } |
                   Select-Object -First 5
    $candidates += Get-ChildItem -Path "C:\ProgramData\Applications\Cache" -Recurse -ErrorAction SilentlyContinue |
                   Where-Object { $_.Name -match "FortiClient.*\.msi$" } |
                   Select-Object -First 5

    if ($candidates.Count -eq 0) {
        Log "No MSI found after extraction attempt."
        return $null
    }

    $msi = $candidates[0].FullName
    $dest = Join-Path $OutFolder (Split-Path $msi -Leaf)
    Copy-Item -Path $msi -Destination $dest -Force
    Log "Extracted MSI to $dest"
    return $dest
}

function Install-FortiClientMsi {
    param([string]$MsiPath)
    Log "Installing MSI: $MsiPath"
    if (!(Test-Path $MsiPath)) { throw "MSI not found: $MsiPath" }

    # Install VPN-only if MSI supports ADDLOCAL=VPN; otherwise do a quiet install.
    $args = "/i `"$MsiPath`" ADDLOCAL=VPN REBOOT=ReallySuppress /qn /L*v `"$LogFolder\FortiClient-install.log`""
    Log "Running: msiexec.exe $args"
    $p = Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -PassThru
    if ($p.ExitCode -eq 0) {
        Log "Install completed successfully."
    } else {
        Log "Install returned exit code $($p.ExitCode). Check installer log."
        throw "Installation failed with exit code $($p.ExitCode)"
    }
}

function Write-VpnProfile {
    param([string]$Name, [string]$Host, [int]$Port)

    $configPath = "C:\ProgramData\Fortinet\FortiClient\vpn\FortiClient.conf"
    $folder = Split-Path $configPath
    if (!(Test-Path $folder)) { New-Item -Path $folder -ItemType Directory -Force | Out-Null }

    # Build server string with host:port
    $server = if ($Port -and $Port -ne 0) { "$Host:$Port" } else { $Host }

    $xml = @"
<forticlient_configuration>
  <vpn>
    <sslvpn>
      <connections>
        <connection>
          <name>$Name</name>
          <server>$server</server>
          <description>Deployed by script</description>
          <prompt_certificate>0</prompt_certificate>
        </connection>
      </connections>
    </sslvpn>
  </vpn>
</forticlient_configuration>
"@

    $xml | Out-File -FilePath $configPath -Encoding utf8 -Force
    Log "Wrote VPN profile to $configPath (name=$Name server=$server)"
}

function Get-FortiClientProduct {
    Log "Querying installed FortiClient product via WMI"
    $apps = Get-WmiObject -Class Win32_Product -Filter "Name LIKE 'FortiClient%'" -ErrorAction SilentlyContinue
    return $apps
}

function Uninstall-FortiClient {
    Log "Attempting uninstall"
    $apps = Get-FortiClientProduct
    if (!$apps) {
        Log "No FortiClient product found."
        return
    }
    foreach ($app in $apps) {
        $guid = $app.IdentifyingNumber
        Log "Uninstalling $($app.Name) GUID $guid"
        $args = "/x $guid /qn /L*v `"$LogFolder\FortiClient-uninstall.log`" REBOOT=ReallySuppress"
        $p = Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -PassThru
        Log "Uninstall exit code $($p.ExitCode)"
    }
}

# Main flow
try {
    Log "Script started. Action = $Action"

    if ($Action -eq "install") {
        # Determine MSI path
        if ($InstallerPath -match "\.msi$") {
            $msi = $InstallerPath
        } else {
            # Try to extract MSI from EXE
            $extractFolder = Join-Path $LogFolder "extracted"
            $msi = Extract-MsiFromExe -ExePath $InstallerPath -OutFolder $extractFolder
            if (-not $msi) {
                # If extraction failed, try running EXE directly with quiet flags (some EXEs support /quiet)
                Log "Falling back to running EXE directly with /quiet"
                $args = "/quiet /norestart"
                $p = Start-Process -FilePath $InstallerPath -ArgumentList $args -Wait -PassThru
                if ($p.ExitCode -ne 0) { throw "EXE installer failed with exit code $($p.ExitCode)" }
                Log "EXE installer completed; attempting to detect installed product."
            }
        }

        if ($msi) {
            Install-FortiClientMsi -MsiPath $msi
        }

        # Write VPN profile
        Write-VpnProfile -Name $ProfileName -Host $GatewayHost -Port $GatewayPort

        # Verify install
        $installed = Get-FortiClientProduct
        if ($installed) {
            Log "FortiClient installed: $($installed.Name) version $($installed.Version)"
        } else {
            Log "FortiClient not detected after install. Check logs."
        }

        Log "Install action complete."
    }
    elseif ($Action -eq "uninstall") {
        Uninstall-FortiClient
        Log "Uninstall action complete."
    }
    else {
        throw "Unknown action: $Action. Use 'install' or 'uninstall'."
    }
}
catch {
    Log "ERROR: $($_.Exception.Message)"
    throw
}
finally {
    Log "Script finished."
}

Quick usage examples

  • Install using an extracted MSI already on disk:
    • Edit $InstallerPath = "C:\Temp\FortiClientVPN.msi" and $Action = "install", then run elevated.
  • Install using the online EXE:
    • Set $InstallerPath = "C:\Temp\FortiClientVPN.exe" and $Action = "install". The script will attempt to extract the MSI and fall back to running the EXE quietly if extraction fails.
  • Uninstall:
    • Set $Action = "uninstall" and run elevated. The script finds FortiClient products via WMI and calls msiexec /x {GUID} silently.

Implementation details and caveats

  • Silent install flags
    • MSI: msiexec /i "FortiClient.msi" ADDLOCAL=VPN REBOOT=ReallySuppress /qn is commonly used for VPN‑only installs; logs can be captured with /L*v.
  • EXE extraction
    • Fortinet’s online EXE often extracts an MSI into a temp/cache folder; the script searches common locations and copies the MSI for a controlled install. If extraction fails, the script attempts a quiet EXE run.
  • Profile deployment
    • Writing FortiClient.conf under C:\ProgramData\Fortinet\FortiClient\vpn\ is a practical way to preconfigure SSL‑VPN connections when EMS is not used. Adjust XML fields to match your environment.
  • Uninstall detection
    • The script uses WMI Win32_Product to find installed FortiClient entries and uninstalls by GUID. In large environments you may prefer to query the registry uninstall keys for performance.

Uninstall FortiClient VPN

FortiClient registers an uninstall GUID. You can query it and remove it silently.

Uninstall‑FortiClientVPN.ps1

 

$App = Get-WmiObject Win32_Product |
       Where-Object { $_.Name -like "FortiClient*" }

if ($App) {
    Start-Process msiexec.exe -ArgumentList @(
        "/x",$App.IdentifyingNumber,
        "/qn",
        "/L*v C:\Temp\FortiClientVPN-uninstall.log"
    ) -Wait
} else {
    Write-Host "FortiClient VPN not found."
}