#Requires -RunAsAdministrator <# .SYNOPSIS Setup-Script fuer Starface Outlook Sync Add-in. .DESCRIPTION - Fragt Starface-Host ab (zum Import des SSL-Zertifikats) - Fragt lokalen Port ab fuer den Webserver - Extrahiert das SSL-Zertifikat der Starface und importiert es als vertrauenswuerdig - Erstellt eine lokale CA und ein localhost-Zertifikat - Installiert den lokalen HTTPS-Webserver als Windows Scheduled Task - Registriert das Outlook Add-in Login-Daten werden NICHT benoetigt - diese werden spaeter im Add-in selbst in den Sync-Profilen konfiguriert. #> param( [string]$InstallDir = "$env:ProgramFiles\StarfaceOutlookSync" ) $ErrorActionPreference = "Stop" $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path # ============================================================ # Hilfsfunktionen # ============================================================ function Write-Header($text) { Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host " $text" -ForegroundColor Cyan Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" } function Write-Step($text) { Write-Host " [OK] $text" -ForegroundColor Green } function Write-Warn($text) { Write-Host " [!] $text" -ForegroundColor Yellow } function Write-Err($text) { Write-Host " [X] $text" -ForegroundColor Red } function Test-NodeJs { try { $version = & node --version 2>$null if ($version) { Write-Step "Node.js gefunden: $version" return $true } } catch {} return $false } function Test-PortAvailable($port) { $listener = Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue return ($null -eq $listener) } function Import-StarfaceCert($host_, $port_) { Write-Step "Verbinde mit ${host_}:${port_} ..." $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.Connect($host_, $port_) $sslStream = New-Object System.Net.Security.SslStream( $tcpClient.GetStream(), $false, { $true } # Alle Zertifikate akzeptieren fuer den Abruf ) $sslStream.AuthenticateAsClient($host_) $remoteCert = $sslStream.RemoteCertificate $x509Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($remoteCert) $sslStream.Close() $tcpClient.Close() Write-Step "Zertifikat erhalten: $($x509Cert.Subject)" Write-Step "Aussteller: $($x509Cert.Issuer)" Write-Step "Gueltig bis: $($x509Cert.NotAfter)" # In den Trusted Root Store importieren $store = New-Object System.Security.Cryptography.X509Certificates.X509Store( [System.Security.Cryptography.X509Certificates.StoreName]::Root, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine ) $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.Add($x509Cert) $store.Close() Write-Step "Starface-Zertifikat als vertrauenswuerdig importiert." return $x509Cert } # ============================================================ # Voraussetzungen pruefen # ============================================================ Write-Header "Starface Outlook Sync - Setup" # Node.js pruefen und ggf. installieren if (-not (Test-NodeJs)) { Write-Warn "Node.js ist nicht installiert." Write-Host " Node.js wird fuer den lokalen Webserver benoetigt." -ForegroundColor Gray Write-Host "" $installNode = Read-Host "Node.js jetzt automatisch herunterladen und installieren? (j/n)" if ($installNode -eq "j") { Write-Step "Ermittle System-Architektur ..." $arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" } $nodeVersion = "v22.14.0" # LTS $msiUrl = "https://nodejs.org/dist/$nodeVersion/node-$nodeVersion-$arch.msi" $msiPath = Join-Path $env:TEMP "node-$nodeVersion-$arch.msi" Write-Step "Lade Node.js $nodeVersion ($arch) herunter ..." Write-Host " $msiUrl" -ForegroundColor Gray try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $ProgressPreference = 'SilentlyContinue' # Beschleunigt den Download Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath -UseBasicParsing if (-not (Test-Path $msiPath)) { Write-Err "Download fehlgeschlagen." exit 1 } $fileSize = [math]::Round((Get-Item $msiPath).Length / 1MB, 1) Write-Step "Download abgeschlossen ($fileSize MB)" Write-Step "Installiere Node.js (bitte warten) ..." $msiArgs = "/i `"$msiPath`" /qn /norestart" $process = Start-Process -FilePath "msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru if ($process.ExitCode -ne 0) { Write-Err "Installation fehlgeschlagen (Exit-Code: $($process.ExitCode))" Write-Host " Bitte Node.js manuell installieren: https://nodejs.org/" -ForegroundColor Yellow exit 1 } # PATH aktualisieren (MSI fuegt Node.js zum System-PATH hinzu) $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") # Aufraeumen Remove-Item $msiPath -Force -ErrorAction SilentlyContinue if (Test-NodeJs) { Write-Step "Node.js erfolgreich installiert!" } else { Write-Err "Node.js wurde installiert, ist aber nicht im PATH gefunden." Write-Host " Bitte das Setup nach einem Neustart erneut ausfuehren." -ForegroundColor Yellow exit 1 } } catch { Write-Err "Fehler beim Download/Installation: $_" Write-Host " Bitte Node.js manuell installieren: https://nodejs.org/" -ForegroundColor Yellow exit 1 } } else { Write-Host " Bitte Node.js manuell installieren: https://nodejs.org/" -ForegroundColor Yellow Write-Host " (LTS-Version empfohlen)" -ForegroundColor Yellow Read-Host "Eingabetaste zum Beenden" exit 1 } } # ============================================================ # Benutzereingaben # ============================================================ Write-Header "Starface-Verbindung" Write-Host " Das Add-in kommuniziert per HTTPS mit der Starface." -ForegroundColor Gray Write-Host " Da die Starface meist ein selbstsigniertes Zertifikat verwendet," -ForegroundColor Gray Write-Host " muss dieses Zertifikat (CA) einmalig als vertrauenswuerdig" -ForegroundColor Gray Write-Host " importiert werden. Dafuer wird hier nur die Adresse benoetigt." -ForegroundColor Gray Write-Host "" -ForegroundColor Gray Write-Host " Login-Daten werden hier NICHT benoetigt - diese konfigurieren" -ForegroundColor Gray Write-Host " Sie spaeter im Add-in in den Sync-Profilen." -ForegroundColor Gray Write-Host "" $starfaceHost = Read-Host "Starface Host/IP-Adresse (z.B. 192.168.1.100 oder pbx.firma.local)" if ([string]::IsNullOrWhiteSpace($starfaceHost)) { Write-Err "Kein Host angegeben." exit 1 } $starfacePortInput = Read-Host "Starface HTTPS-Port [443]" $starfacePort = if ([string]::IsNullOrWhiteSpace($starfacePortInput)) { 443 } else { [int]$starfacePortInput } Write-Host "" Write-Header "Lokaler Webserver" $localPortInput = Read-Host "Lokaler HTTPS-Port fuer das Add-in [444]" $localPort = if ([string]::IsNullOrWhiteSpace($localPortInput)) { 444 } else { [int]$localPortInput } if (-not (Test-PortAvailable $localPort)) { Write-Warn "Port $localPort ist bereits belegt!" $localPortInput = Read-Host "Bitte einen anderen Port waehlen" $localPort = [int]$localPortInput if (-not (Test-PortAvailable $localPort)) { Write-Err "Port $localPort ist ebenfalls belegt. Abbruch." exit 1 } } Write-Step "Verwende Port: $localPort" # ============================================================ # Schritt 1: Starface SSL-Zertifikat extrahieren und importieren # ============================================================ Write-Header "Schritt 1: Starface-Zertifikat importieren" try { Import-StarfaceCert $starfaceHost $starfacePort } catch { Write-Err "Fehler beim Abrufen des Starface-Zertifikats: $_" Write-Warn "Die Verbindung zur Starface API koennte fehlschlagen." Write-Warn "Sie koennen das Zertifikat spaeter nachholen mit:" Write-Host " .\import-cert.ps1 -StarfaceHost $starfaceHost -Port $starfacePort" -ForegroundColor White Write-Host "" Write-Host " Moechten Sie trotzdem fortfahren? (j/n)" -ForegroundColor Yellow $continue = Read-Host if ($continue -ne "j") { exit 1 } } # ============================================================ # Schritt 2: Lokale CA und localhost-Zertifikat erstellen # ============================================================ Write-Header "Schritt 2: Lokale Zertifikate erstellen" $certDir = Join-Path $InstallDir "certs" New-Item -ItemType Directory -Path $certDir -Force | Out-Null # Lokale CA erstellen Write-Step "Erstelle lokale CA ..." $caParams = @{ Subject = "CN=Starface Outlook Sync Local CA" KeyLength = 2048 KeyAlgorithm = "RSA" HashAlgorithm = "SHA256" KeyExportPolicy = "Exportable" NotAfter = (Get-Date).AddYears(10) CertStoreLocation = "Cert:\LocalMachine\My" KeyUsage = "CertSign", "CRLSign" TextExtension = @("2.5.29.19={text}CA=true&pathlength=1") } $caCert = New-SelfSignedCertificate @caParams Write-Step "Lokale CA erstellt: $($caCert.Thumbprint)" # CA in Trusted Root Store importieren $rootStore = New-Object System.Security.Cryptography.X509Certificates.X509Store( [System.Security.Cryptography.X509Certificates.StoreName]::Root, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine ) $rootStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $rootStore.Add($caCert) $rootStore.Close() Write-Step "Lokale CA als vertrauenswuerdig importiert." # Localhost-Zertifikat erstellen, signiert von der lokalen CA Write-Step "Erstelle localhost-Zertifikat ..." $localhostParams = @{ Subject = "CN=localhost" KeyLength = 2048 KeyAlgorithm = "RSA" HashAlgorithm = "SHA256" KeyExportPolicy = "Exportable" NotAfter = (Get-Date).AddYears(5) CertStoreLocation = "Cert:\LocalMachine\My" Signer = $caCert DnsName = "localhost", "127.0.0.1" TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.1") # Server Authentication } $localhostCert = New-SelfSignedCertificate @localhostParams Write-Step "Localhost-Zertifikat erstellt: $($localhostCert.Thumbprint)" # Zertifikat als PFX exportieren (fuer Node.js) # PFX funktioniert auf allen Windows-Versionen (.NET Framework + .NET Core) Write-Step "Exportiere Zertifikate fuer den Webserver ..." $pfxPassword = [guid]::NewGuid().ToString() $pfxSecure = ConvertTo-SecureString -String $pfxPassword -Force -AsPlainText $pfxPath = Join-Path $certDir "localhost.pfx" Export-PfxCertificate -Cert $localhostCert -FilePath $pfxPath -Password $pfxSecure | Out-Null # Passwort in config speichern (wird vom Server gelesen) Set-Content -Path (Join-Path $certDir "pfx-password.txt") -Value $pfxPassword -Encoding ASCII Write-Step "PFX-Zertifikat exportiert." # CA-Zertifikat exportieren (fuer Deinstallation) $caCertPem = "-----BEGIN CERTIFICATE-----`n" $caCertPem += [Convert]::ToBase64String($caCert.RawData, [Base64FormattingOptions]::InsertLineBreaks) $caCertPem += "`n-----END CERTIFICATE-----" Set-Content -Path (Join-Path $certDir "local-ca.crt") -Value $caCertPem -Encoding ASCII # Thumbprints speichern (fuer Deinstallation) $certInfo = @{ caThumbprint = $caCert.Thumbprint localhostThumbprint = $localhostCert.Thumbprint } $certInfo | ConvertTo-Json | Set-Content (Join-Path $InstallDir "cert-info.json") -Encoding UTF8 Write-Step "Zertifikate exportiert nach: $certDir" # ============================================================ # Schritt 3: Add-in Dateien installieren # ============================================================ Write-Header "Schritt 3: Add-in Dateien installieren" $webrootDir = Join-Path $InstallDir "webroot" New-Item -ItemType Directory -Path $webrootDir -Force | Out-Null # Pruefen ob dist-Ordner existiert (bereits gebaut) $distDir = Join-Path $scriptDir "..\dist" if (-not (Test-Path $distDir)) { Write-Step "Baue Add-in (npm run build) ..." Push-Location (Join-Path $scriptDir "..") & npm run build 2>&1 | Out-Null Pop-Location } if (-not (Test-Path $distDir)) { Write-Err "Build fehlgeschlagen. dist-Ordner nicht gefunden." exit 1 } # Dateien kopieren Copy-Item -Path "$distDir\*" -Destination $webrootDir -Recurse -Force Write-Step "Add-in Dateien kopiert nach: $webrootDir" # manifest.xml mit korrektem Port anpassen $manifestPath = Join-Path $webrootDir "manifest.xml" if (Test-Path $manifestPath) { $manifestContent = Get-Content $manifestPath -Raw -Encoding UTF8 $manifestContent = $manifestContent -replace "https://localhost:3000", "https://localhost:$localPort" Set-Content -Path $manifestPath -Value $manifestContent -Encoding UTF8 Write-Step "manifest.xml angepasst (Port: $localPort)" } # Auch eine Kopie der manifest.xml ins Installationsverzeichnis Copy-Item -Path $manifestPath -Destination (Join-Path $InstallDir "manifest.xml") -Force # ============================================================ # Schritt 4: Server-Konfiguration und Installation # ============================================================ Write-Header "Schritt 4: Lokalen Webserver einrichten" # Server-Dateien kopieren Copy-Item -Path (Join-Path $scriptDir "local-server.js") -Destination $InstallDir -Force # import-cert.ps1 ins Installationsverzeichnis kopieren (fuer spaetere Nutzung) $importCertSrc = Join-Path $scriptDir "import-cert.ps1" if (Test-Path $importCertSrc) { Copy-Item -Path $importCertSrc -Destination $InstallDir -Force } # uninstall.ps1 ins Installationsverzeichnis kopieren $uninstallSrc = Join-Path $scriptDir "uninstall.ps1" if (Test-Path $uninstallSrc) { Copy-Item -Path $uninstallSrc -Destination $InstallDir -Force } # config.json erstellen $serverConfig = @{ port = $localPort } $serverConfig | ConvertTo-Json | Set-Content (Join-Path $InstallDir "config.json") -Encoding UTF8 Write-Step "Server-Konfiguration erstellt." # Windows Scheduled Task erstellen (startet beim Systemstart) $taskName = "StarfaceOutlookSyncServer" # Bestehenden Task entfernen falls vorhanden Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue $nodeExe = (Get-Command node).Source $serverScript = Join-Path $InstallDir "local-server.js" $action = New-ScheduledTaskAction -Execute $nodeExe -Argument "`"$serverScript`"" -WorkingDirectory $InstallDir $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit ([TimeSpan]::Zero) Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Lokaler HTTPS-Server fuer Starface Outlook Sync Add-in" | Out-Null # Task sofort starten Start-ScheduledTask -TaskName $taskName Start-Sleep -Seconds 2 # Pruefen ob der Server laeuft try { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } $response = Invoke-WebRequest -Uri "https://localhost:$localPort/taskpane.html" -UseBasicParsing -TimeoutSec 5 if ($response.StatusCode -eq 200) { Write-Step "Lokaler Webserver laeuft auf https://localhost:$localPort" } } catch { Write-Warn "Server-Test fehlgeschlagen. Server startet moeglicherweise verzoegert." } # ============================================================ # Schritt 5: Outlook Add-in registrieren # ============================================================ Write-Header "Schritt 5: Outlook Add-in registrieren" # Manifest-Datei vorbereiten $manifestDir = Join-Path $InstallDir "manifest-catalog" New-Item -ItemType Directory -Path $manifestDir -Force | Out-Null Copy-Item -Path (Join-Path $InstallDir "manifest.xml") -Destination $manifestDir -Force $manifestFile = Join-Path $manifestDir "manifest.xml" # Outlook Add-ins werden pro Benutzer ueber den Browser registriert. # Das Add-in wird am Microsoft 365 Konto des Benutzers gespeichert, # nicht am Computer. Auf einem Terminal Server muss jeder Benutzer # das Add-in einmalig selbst hinzufuegen. Write-Host " Das Add-in muss pro Benutzer einmalig in Outlook" -ForegroundColor White Write-Host " hinzugefuegt werden. Es wird am Microsoft 365 Konto" -ForegroundColor White Write-Host " gespeichert und ist dann auf allen Geraeten verfuegbar." -ForegroundColor White Write-Host "" Write-Host " So geht's:" -ForegroundColor Cyan Write-Host " 1. Im Browser oeffnen: https://aka.ms/olksideload" -ForegroundColor White Write-Host " 2. Mit dem eigenen Microsoft 365 Konto anmelden" -ForegroundColor White Write-Host " 3. 'Meine Add-Ins' -> 'Benutzerdefinierte Add-Ins'" -ForegroundColor White Write-Host " 4. 'Benutzerdefiniertes Add-In hinzufuegen' -> 'Aus Datei'" -ForegroundColor White Write-Host " 5. Diese Datei waehlen:" -ForegroundColor White Write-Host " $manifestFile" -ForegroundColor Yellow Write-Host "" Write-Host " Alternativ direkt in Outlook:" -ForegroundColor Cyan Write-Host " Classic: Datei -> Info -> 'Add-Ins verwalten'" -ForegroundColor White Write-Host " Neu: Einstellungen -> Add-Ins verwalten" -ForegroundColor White Write-Host "" $openBrowser = Read-Host "Sideload-Seite jetzt im Browser oeffnen? (j/n)" if ($openBrowser -eq "j") { Start-Process "https://aka.ms/olksideload" Write-Step "Browser geoeffnet." Write-Host " Bitte Add-in wie oben beschrieben hinzufuegen." -ForegroundColor White Write-Host " Manifest-Datei: $manifestFile" -ForegroundColor Yellow } # ============================================================ # Schritt 6: Installationsinfo speichern # ============================================================ $installInfo = @{ installDir = $InstallDir localPort = $localPort taskName = $taskName installedAt = (Get-Date).ToString("o") manifestDir = $manifestDir manifestFile = $manifestFile manifestUrl = "https://localhost:$localPort" caThumbprint = $caCert.Thumbprint localhostThumbprint = $localhostCert.Thumbprint } $installInfo | ConvertTo-Json | Set-Content (Join-Path $InstallDir "install-info.json") -Encoding UTF8 # ============================================================ # Zusammenfassung # ============================================================ Write-Header "Installation abgeschlossen!" Write-Host " Installationsverzeichnis: $InstallDir" -ForegroundColor White Write-Host " Lokaler Server: https://localhost:$localPort" -ForegroundColor White Write-Host " Manifest: $manifestFile" -ForegroundColor White Write-Host "" Write-Host " [!] Jeder Benutzer muss das Add-in einmalig in Outlook" -ForegroundColor Yellow Write-Host " hinzufuegen: https://aka.ms/olksideload" -ForegroundColor Yellow Write-Host " Manifest-Datei: $manifestFile" -ForegroundColor Yellow Write-Host "" Write-Host " Naechster Schritt: Im Add-in ein Sync-Profil mit den" -ForegroundColor White Write-Host " Starface-Zugangsdaten einrichten." -ForegroundColor White Write-Host "" Write-Host " Weitere Starface-Anlagen einbinden? Zertifikat importieren mit:" -ForegroundColor Gray Write-Host " .\import-cert.ps1 -StarfaceHost [-Port ]" -ForegroundColor Gray Write-Host "" Write-Host " Deinstallation: uninstall.ps1 als Administrator ausfuehren" -ForegroundColor Gray Write-Host "" # Firewall-Regel fuer den lokalen Port (nur localhost, rein vorsichtshalber) New-NetFirewallRule -DisplayName "Starface Outlook Sync" -Direction Inbound -LocalPort $localPort -Protocol TCP -Action Allow -Profile Private -ErrorAction SilentlyContinue | Out-Null Read-Host "Eingabetaste zum Beenden"