From 83d8edd1334feca1e99678426473a542187960c4 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:21:38 +0300 Subject: [PATCH] Initial commit --- .gitignore | 3 + distr/!README.docx | Bin 0 -> 43740 bytes src/CD_GenerateManifest.ps1 | 18 ++ src/CD_Install.ps1 | 10 + src/CD_Tests.ps1 | 9 + src/ConceptDeploy/ConceptDeploy.psd1 | Bin 0 -> 8566 bytes src/ConceptDeploy/ConceptDeploy.psm1 | 9 + src/ConceptDeploy/Disable-Product.ps1 | 44 +++ src/ConceptDeploy/Enable-Product.ps1 | 47 ++++ src/ConceptDeploy/Expand-TarGZ.ps1 | 44 +++ src/ConceptDeploy/Get-ConceptLocal.ps1 | 4 + src/ConceptDeploy/Get-ExchangePath.ps1 | 10 + src/ConceptDeploy/Get-ProjectsPath.ps1 | 10 + src/ConceptDeploy/Get-ServerDistr.ps1 | 10 + src/ConceptDeploy/Initialize-Python.ps1 | 165 +++++++++++ src/ConceptDeploy/Install-PSLatest.ps1 | 4 + src/ConceptDeploy/Install-Product.ps1 | 122 ++++++++ src/ConceptDeploy/Install-PythonLibs.ps1 | 42 +++ src/ConceptDeploy/Open-DistrManifest.ps1 | 16 ++ src/ConceptDeploy/Push-DLL.ps1 | 22 ++ src/ConceptDeploy/Push-PythonPackage.ps1 | 49 ++++ src/ConceptDeploy/Save-DistrManifest.ps1 | 12 + src/DeployDistributives.ps1 | 68 +++++ src/DeployServer.ps1 | 34 +++ src/InstallAllProducts.ps1 | 344 +++++++++++++++++++++++ src/UninstallConceptProducts.ps1 | 127 +++++++++ src/UpdateConceptProducts.ps1 | 162 +++++++++++ src/VERSION | 1 + src/install_from_server.bat | 7 + src/install_launcher.bat | 7 + src/install_standalone.bat | 7 + src/uninstall.bat | 7 + 32 files changed, 1414 insertions(+) create mode 100644 .gitignore create mode 100644 distr/!README.docx create mode 100644 src/CD_GenerateManifest.ps1 create mode 100644 src/CD_Install.ps1 create mode 100644 src/CD_Tests.ps1 create mode 100644 src/ConceptDeploy/ConceptDeploy.psd1 create mode 100644 src/ConceptDeploy/ConceptDeploy.psm1 create mode 100644 src/ConceptDeploy/Disable-Product.ps1 create mode 100644 src/ConceptDeploy/Enable-Product.ps1 create mode 100644 src/ConceptDeploy/Expand-TarGZ.ps1 create mode 100644 src/ConceptDeploy/Get-ConceptLocal.ps1 create mode 100644 src/ConceptDeploy/Get-ExchangePath.ps1 create mode 100644 src/ConceptDeploy/Get-ProjectsPath.ps1 create mode 100644 src/ConceptDeploy/Get-ServerDistr.ps1 create mode 100644 src/ConceptDeploy/Initialize-Python.ps1 create mode 100644 src/ConceptDeploy/Install-PSLatest.ps1 create mode 100644 src/ConceptDeploy/Install-Product.ps1 create mode 100644 src/ConceptDeploy/Install-PythonLibs.ps1 create mode 100644 src/ConceptDeploy/Open-DistrManifest.ps1 create mode 100644 src/ConceptDeploy/Push-DLL.ps1 create mode 100644 src/ConceptDeploy/Push-PythonPackage.ps1 create mode 100644 src/ConceptDeploy/Save-DistrManifest.ps1 create mode 100644 src/DeployDistributives.ps1 create mode 100644 src/DeployServer.ps1 create mode 100644 src/InstallAllProducts.ps1 create mode 100644 src/UninstallConceptProducts.ps1 create mode 100644 src/UpdateConceptProducts.ps1 create mode 100644 src/VERSION create mode 100644 src/install_from_server.bat create mode 100644 src/install_launcher.bat create mode 100644 src/install_standalone.bat create mode 100644 src/uninstall.bat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a66c69d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +~$* + +src/logs/* \ No newline at end of file diff --git a/distr/!README.docx b/distr/!README.docx new file mode 100644 index 0000000000000000000000000000000000000000..d78dbd7f55e52f4834b909c3bbf09509420d8011 GIT binary patch literal 43740 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

p$qwiV+9J+TI}hw~dg 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 literal 0 HcmV?d00001 diff --git a/src/ConceptDeploy/ConceptDeploy.psm1 b/src/ConceptDeploy/ConceptDeploy.psm1 new file mode 100644 index 0000000..72924ed --- /dev/null +++ b/src/ConceptDeploy/ConceptDeploy.psm1 @@ -0,0 +1,9 @@ +$localPath = $script:MyInvocation.MyCommand.Path + +Get-ChildItem (Split-Path $localPath) -Filter '*.ps1' -Recurse | ForEach-Object { + . $_.FullName +} + +Get-ChildItem "$(Split-Path $localPath)" -Filter '*.ps1' -Recurse | ForEach-Object { + Export-ModuleMember -Function $_.BaseName +} \ No newline at end of file diff --git a/src/ConceptDeploy/Disable-Product.ps1 b/src/ConceptDeploy/Disable-Product.ps1 new file mode 100644 index 0000000..a3eba4d --- /dev/null +++ b/src/ConceptDeploy/Disable-Product.ps1 @@ -0,0 +1,44 @@ +function Disable-Product { + [CmdletBinding()] + Param( + [string] $product + ) + + if ($product -eq 'ExcelHelper') { + return RemoveExcelAddin 'CONCEPT.xlam' + } elseif ($product -eq 'WordHelper') { + return RemoveWordAddin 'CONCEPT.dotm' + } elseif ($product -eq 'Concept-Reports') { + return RemoveExcelAddin 'ConceptReport.xlam' + } elseif ($product -eq 'Concept-Maket') { + return RemoveWordAddin '_Maket.dotm' + } elseif ($product -eq 'Concept-NPA') { + return RemoveWordAddin '_Concept-NPA.dotm' + } elseif ($product -eq 'Concept-Mining') { + return RemoveWordAddin 'Parsers.dotm' + } elseif ($product -eq 'Concept-Markup') { + return RemoveWordAddin 'MARKUP.dotm' + } + return $false +} + +function TryDelete($fullName) { + try { + Remove-Item -Path $fullName + return $true + } catch { + return $false + } +} + +function RemoveExcelAddin($fileName) { + $fullName = "$($Env:APPDATA)\Microsoft\Excel\XLSTART\$fileName" + Write-Host "Removing addin $fullName" + return TryDelete $fullName +} + +function RemoveWordAddin($fileName) { + $fullName = "$($Env:APPDATA)\Microsoft\Word\STARTUP\$fileName" + Write-Host "Removing addin $fullName" + return TryDelete $fullName +} \ No newline at end of file diff --git a/src/ConceptDeploy/Enable-Product.ps1 b/src/ConceptDeploy/Enable-Product.ps1 new file mode 100644 index 0000000..2032632 --- /dev/null +++ b/src/ConceptDeploy/Enable-Product.ps1 @@ -0,0 +1,47 @@ +function Enable-Product { + [CmdletBinding()] + Param( + [string] $product, + [string] $localTools + ) + + if ($product -eq 'ExcelHelper') { + return CopyExcelAddin "$localTools\optional" 'CONCEPT.xlam' + } elseif ($product -eq 'WordHelper') { + return CopyWordAddin "$localTools\optional" 'CONCEPT.dotm' + } elseif ($product -eq 'Concept-Reports') { + return CopyExcelAddin "$localTools\optional" 'ConceptReport.xlam' + } elseif ($product -eq 'Concept-Maket') { + return CopyWordAddin "$localTools\optional" '_Maket.dotm' + } elseif ($product -eq 'Concept-NPA') { + return CopyWordAddin "$localTools\optional" '_Concept-NPA.dotm' + } elseif ($product -eq 'Concept-Mining') { + return CopyWordAddin "$localTools\optional" 'Parsers.dotm' + } elseif ($product -eq 'Concept-Markup') { + return CopyWordAddin "$localTools\optional" 'MARKUP.dotm' + } + return $false +} + +function TryCopy($source, $destination) { + try { + Copy-Item -Path $source -Destination $destination -Force -Recurse -ErrorAction Stop + return $true + } catch { + return $false + } +} + +function CopyExcelAddin($location, $fileName) { + $source = "$location\$fileName" + $destination = "$($Env:APPDATA)\Microsoft\Excel\XLSTART\" + Write-Host "Copying addin $fileName to $destination" + return TryCopy $source $destination +} + +function CopyWordAddin($location, $fileName) { + $source = "$location\$fileName" + $destination = "$($Env:APPDATA)\Microsoft\Word\STARTUP\" + Write-Host "Copying addin $fileName to $destination" + return TryCopy $source $destination +} \ No newline at end of file diff --git a/src/ConceptDeploy/Expand-TarGZ.ps1 b/src/ConceptDeploy/Expand-TarGZ.ps1 new file mode 100644 index 0000000..955b216 --- /dev/null +++ b/src/ConceptDeploy/Expand-TarGZ.ps1 @@ -0,0 +1,44 @@ +Function Expand-TarGZ { + [CmdletBinding()] + Param( + [string] $inFile, + [string] $outFolder = ($inFile -replace '\.tar.gz$','') + ) + + $tarFile = Expand-GZ -InFile $inFile + if (-not $tarFile) { + Write-Error "Cannot unpack gzip $inFile into $tarFile" + Exit + } + + if (-not (Get-Command Expand-7Zip -ErrorAction Ignore)) { + Install-Package -Scope CurrentUser -Force 7Zip4PowerShell > $null + } + Expand-7Zip $tarFile $outFolder + + Remove-Item $tarFile -Recurse -Force + return $outFolder +} + +Function Expand-GZ([string] $inFile) { + $outFile = ($inFile -replace '\.gz$','') + if (Test-Path -Path $outFile -PathType leaf) { + Remove-Item -Path $outFile -Force + } + $BUFFER_SIZE = 1024 + $inputStream = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read) + $outputStream = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None) + $gzipStream = New-Object System.IO.Compression.GzipStream $inputStream, ([IO.Compression.CompressionMode]::Decompress) + + $buffer = New-Object byte[]($BUFFER_SIZE) + while($true) { + $read = $gzipstream.Read($buffer, 0, $BUFFER_SIZE) + if ($read -le 0){break} + $outputStream.Write($buffer, 0, $read) + } + + $gzipStream.Close() + $outputStream.Close() + $inputStream.Close() + return $outFile +} \ No newline at end of file diff --git a/src/ConceptDeploy/Get-ConceptLocal.ps1 b/src/ConceptDeploy/Get-ConceptLocal.ps1 new file mode 100644 index 0000000..9726e27 --- /dev/null +++ b/src/ConceptDeploy/Get-ConceptLocal.ps1 @@ -0,0 +1,4 @@ +# Return path to local Concept CIHT files +function Get-ConceptLocal { + return "$Env:USERPROFILE\.concept" +} \ No newline at end of file diff --git a/src/ConceptDeploy/Get-ExchangePath.ps1 b/src/ConceptDeploy/Get-ExchangePath.ps1 new file mode 100644 index 0000000..dd41aeb --- /dev/null +++ b/src/ConceptDeploy/Get-ExchangePath.ps1 @@ -0,0 +1,10 @@ +# Get exchange server folder for CIHT CONCEPT +function Get-ExchangePath { + $server = '\\fs1.concept.ru\Exchange' + if (-not (Test-Path -Path $server)) { + Write-Error "Failed to locate server $server. Ensure VPN is running" + Exit + } else { + return $server + } +} \ No newline at end of file diff --git a/src/ConceptDeploy/Get-ProjectsPath.ps1 b/src/ConceptDeploy/Get-ProjectsPath.ps1 new file mode 100644 index 0000000..4470c73 --- /dev/null +++ b/src/ConceptDeploy/Get-ProjectsPath.ps1 @@ -0,0 +1,10 @@ +# Get projects server folder for CIHT CONCEPT +function Get-ProjectsPath { + $server = '\\fs1.concept.ru\projects' + if (-not (Test-Path -Path $server)) { + Write-Error "Failed to locate server $server. Ensure VPN is running" + Exit + } else { + return $server + } +} \ No newline at end of file diff --git a/src/ConceptDeploy/Get-ServerDistr.ps1 b/src/ConceptDeploy/Get-ServerDistr.ps1 new file mode 100644 index 0000000..726841e --- /dev/null +++ b/src/ConceptDeploy/Get-ServerDistr.ps1 @@ -0,0 +1,10 @@ +# Get server code distribution folder for CIHT CONCEPT +function Get-ServerDistr { + $server = '\\fs1.concept.ru\projects\10 Автоматизация деятельности\!Concept' + if (-not (Test-Path -Path $server)) { + Write-Error "Failed to locate server $server. Ensure VPN is running" + Exit + } else { + return $server + } +} \ No newline at end of file diff --git a/src/ConceptDeploy/Initialize-Python.ps1 b/src/ConceptDeploy/Initialize-Python.ps1 new file mode 100644 index 0000000..16e4721 --- /dev/null +++ b/src/ConceptDeploy/Initialize-Python.ps1 @@ -0,0 +1,165 @@ +function Initialize-Python { + [CmdletBinding()] + Param( + [string] $minimalVersion = '3.12.2', + [string] $installPath = 'C:\Tools\Python312-venv', + [string] $pythonInstall = 'C:\Tools\Python312', + [string] $pythonDistr = '' + ) + $python = SearchInstalledEnvironment -TargetPath $installPath -MinimalVersion $minimalVersion + if ($python -ne '') { + return $python + } + $python = SearchLocalInstallation -TargetPath $pythonInstall -MinimalVersion $minimalVersion + if ($python -eq '') { + Write-Host "Failed to locate local installation of python" + $python = SearchUserPath -MinimalVersion $minimalVersion + } + if ($python -eq '') { + Write-Host "Failed to locate python in PATH variable" + $python = SearchUserRegistry -MinimalVersion $minimalVersion + } + if ($python -eq '') { + Write-Host "Failed to locate python in Windows registry" + $python = InstallLocalPython -Version $minimalVersion -TargetPath $pythonInstall -PythonDistr $pythonDistr + } + if ($python -eq '') { + return '' + } + CreateVirtualEnvironment -Python $python -TargetPath $installPath + return "$installPath\Scripts\python.exe" +} + +function SearchInstalledEnvironment($targetPath, $minimalVersion) { + $python = "$targetPath\Scripts\python.exe" + if (-not (Test-Path -Path $python -PathType Leaf)) { + Write-Host "Failed to locate Python venv at $targetPath" + return '' + } + $installedVersion = GetPythonVersion $python + if(!$installedVersion -or + $installedVersion -eq '' -or + [System.Version] $installedVersion -lt [System.Version] $minimalVersion) { + Write-Host "Removing invalid python env: $targetPath" -ForegroundColor DarkRed + Remove-Item $targetPath -Recurse + return '' + } else { + Write-Host "Found Python environment $installedVersion : $python" + return $python + } +} + +function SearchLocalInstallation($targetPath, $minimalVersion) { + $python = "$targetPath\python.exe" + if (-not (Test-Path -Path $python -PathType Leaf)) { + return '' + } + $installedVersion = GetPythonVersion $python + if(!$installedVersion -or + $installedVersion -eq '' -or + [System.Version] $installedVersion -ne [System.Version] $minimalVersion) { + return '' + } + Write-Host "Found installed python $installedVersion : $python" + return $python +} + +function SearchUserPath($minimalVersion) { + $globalVersion = GetPythonVersion 'python' + if(!$globalVersion -or + $globalVersion -eq '' -or + [System.Version] $globalVersion -ne [System.Version] $minimalVersion) { + return '' + } + Write-Host "Found globally installed python: $globalVersion" + return 'python' +} + +function SearchUserRegistry($minimalVersion) { + if ([Environment]::Is64BitProcess) { + $pythonRegistry = 'HKCU:\SOFTWARE\Python\PythonCore\3.12\InstallPath' + if(!$pythonRegistry) { + $pythonRegistry = 'HKLM:\SOFTWARE\Python\PythonCore\3.12\InstallPath' + } + } else { + $pythonRegistry = 'HKCU:\SOFTWARE\WOW6432Node\Python\PythonCore\3.12-32\InstallPath' + if(!$pythonRegistry) { + $pythonRegistry = 'HKLM:\SOFTWARE\WOW6432Node\Python\PythonCore\3.12-32\InstallPath' + } + } + $python = Get-ItemPropertyValue -Path $pythonRegistry -Name 'ExecutablePath' -ErrorAction 'SilentlyContinue' + if (-not $python) { + return '' + } + $version = GetPythonVersion $python + if(!$version -or + $version -eq '' -or + [System.Version] $version -lt [System.Version] $minimalVersion) { + return '' + } + Write-Host "Found installed local python: $python" -ForegroundColor DarkGreen + return $python +} + +function InstallLocalPython($version, $targetPath, $pythonDistr = '') { + if ($pythonDistr -eq '') { + $installer = DowloadPython $version + } else { + $installer = $pythonDistr + } + + $arguments = "/quiet InstallAllUsers=0 Include_test=0 Include_doc=0 Include_launcher=0 TargetDir=$targetPath" + $process = (Start-Process -FilePath $installer -ArgumentList $arguments -PassThru -NoNewWindow) + $process.WaitForExit() + + if ($pythonDistr -eq '') { + Remove-Item $installer -ErrorAction SilentlyContinue + } + + $python = "$targetPath\python.exe" + if (-not (Test-Path -Path $python -PathType Leaf)) { + Write-Error "Failed to install local python: $installer" + return '' + } else { + Write-Host "Installed local python: $targetPath" -ForegroundColor DarkGreen + return $python + } +} + +function GetPythonVersion($pythonExe) { + $eap = $ErrorActionPreference + $ErrorActionPreference = 'SilentlyContinue' + $version = & $pythonExe --version + $ErrorActionPreference = $eap + if (!$version -or $version -eq '' -or $LASTEXITCODE -ne 0) { + return '' + } else { + return $version.Split(' ')[1] + } +} + +function DowloadPython($version) { + $pythonUrl = GetPythonURL $version + $pythonTemp = "$Env:TEMP\installer-python-$version.exe" + Write-Host "Downloading python installer to $pythonTemp" + try { + (New-Object System.Net.WebClient).DownloadFile($pythonUrl, $pythonTemp) + } catch [System.Net.WebException] { + Write-Error "An exception was caught when trying to download Python: $($_.Exception.Message)" + Exit + } + return $pythonTemp +} + +function CreateVirtualEnvironment($python, $targetPath) { + & $python -m venv $targetPath + Write-Host "Created virtual environment $targetPath" -ForegroundColor DarkGreen +} + +function GetPythonURL($version) { + if ([Environment]::Is64BitProcess) { + return "https://www.python.org/ftp/python/$version/python-$version-amd64.exe" + } else { + return = "https://www.python.org/ftp/python/$version/python-$version.exe" + } +} \ No newline at end of file diff --git a/src/ConceptDeploy/Install-PSLatest.ps1 b/src/ConceptDeploy/Install-PSLatest.ps1 new file mode 100644 index 0000000..1393a42 --- /dev/null +++ b/src/ConceptDeploy/Install-PSLatest.ps1 @@ -0,0 +1,4 @@ +# Download and install latest version of powershell +function Install-PSLatest { + Invoke-Expression "& { $(Invoke-RestMethod https://aka.ms/install-powershell.ps1) } -UseMSI -Quiet" +} \ No newline at end of file diff --git a/src/ConceptDeploy/Install-Product.ps1 b/src/ConceptDeploy/Install-Product.ps1 new file mode 100644 index 0000000..9f1fe44 --- /dev/null +++ b/src/ConceptDeploy/Install-Product.ps1 @@ -0,0 +1,122 @@ +# Install concept product +function Install-Product { + [CmdletBinding()] + Param( + [string] $product, + [string] $distr, + [string] $localTemplates, + [string] $localTools + ) + + if ($product -eq 'ExcelHelper') { + return InstallExcelAddinOptional $distr 'CONCEPT.xlam' $localTools + } elseif ($product -eq 'WordHelper') { + return $(InstallWordAddinOptional $distr 'CONCEPT.dotm' $localTools -and + InstallUserModel $distr 'banned-words.txt') + } elseif ($product -eq 'Concept-Maket') { + return InstallWordAddinOptional $distr '_Maket.dotm' $localTools + } elseif ($product -eq 'Concept-Reports') { + return InstallExcelAddinOptional $distr "ConceptReport.xlam" $localTools + } elseif ($product -eq 'Concept-Hierarchy') { + return InstallTemplate $distr '30 Иерархизатор.vstm' $localTemplates + } elseif ($product -eq 'Concept-Blocks') { + return $(InstallTemplate $distr '20 Концепт-Блоки.vstm' $localTemplates -and + InstallTemplate $distr 'Технологии\Блоки-Excel.xltx' $localTemplates -and + InstallTemplate $distr 'Технологии\Блоки-Word.dotx' $localTemplates) + } elseif ($product -eq 'Concept-Defs') { + return $(InstallTemplate $distr '22 Карта понятий.vstm' $localTemplates -and + InstallTemplate $distr 'Технологии\Определения-Excel.xltx' $localTemplates) + } elseif ($product -eq 'Concept-NPA') { + Write-Host "Copying file !Реестр НПА.xlsm" + Copy-Item -Path "$distr\data\!Реестр НПА.xlsm" -Destination "$localTools\optional\" -Force + return $(InstallTemplate $distr '60 НПА UI.xltm' $localTemplates -and + InstallTemplate $distr '21 Схема Реестра НПА.vstm' $localTemplates -and + InstallWordAddinOptional $distr '_Concept-NPA.dotm' $localTools) + } elseif ($product -eq 'Concept-Mining') { + return $(InstallTemplate $distr '55 Майнинг.xltm' $localTemplates -and + InstallWordAddinOptional $distr 'Parsers.dotm' $localTools -and + InstallLocalModel $distr 'ActionVerbs.txt' $localTools) + } elseif ($product -eq 'Concept-Markup') { + return $(InstallTemplate $distr 'Разметка' $localTemplates -and + InstallWordAddinOptional $distr 'MARKUP.dotm' $localTools) + } + return $false +} + +function TryCopy($source, $destination) { + try { + Copy-Item -Path $source -Destination $destination -Force -Recurse -ErrorAction Stop + return $true + } catch { + return $false + } +} + +function InstallUserModel($distr, $fileName) { + $source = "$distr\models\$fileName" + $destination = "$(Get-ConceptLocal)\models\" + Write-Host "Update user model $fileName at $destination" + return TryCopy $source $destination +} + +function InstallLocalModel($distr, $fileName, $localTools) { + $source = "$distr\models\$fileName" + $destination = "$localTools\models\" + Write-Host "Copying model file $fileName to $destination" + return TryCopy $source $destination +} + +function InstallTemplate($distr, $fileName, $templates) { + $source = "$distr\data\Templates\$fileName" + $destination = "$templates\" + Write-Host "Copying template $fileName to $destination" + return TryCopy $source $destination +} + +function InstallExcelAddin($distr, $fileName) { + $source = "$distr\data\Add-ins\Excel\$fileName" + $destination = "$($Env:APPDATA)\Microsoft\Excel\XLSTART\" + Write-Host "Copying addin $fileName to $destination" + return TryCopy $source $destination +} + +function InstallExcelAddinOptional($distr, $fileName, $localTools) { + $source = "$distr\data\Add-ins\Excel\$fileName" + + $destination1 = "$localTools\optional\" + Write-Host "Copying optional addin $fileName to $destination1" + if (-not (TryCopy $source $destination1)) { + return $false + } + + $destination2 = "$($Env:APPDATA)\Microsoft\Excel\XLSTART\" + if (Test-Path -Path "$destination2\$fileName" -PathType Leaf) { + Write-Host "Copying optional addin $fileName to $destination2" + return TryCopy $source $destination2 + } + return $true +} + +function InstallWordAddin($distr, $fileName) { + $source = "$distr\data\Add-ins\Word\$fileName" + $destination = "$($Env:APPDATA)\Microsoft\Word\STARTUP\" + Write-Host "Copying addin $fileName to $destination" + return TryCopy $source $destination +} + +function InstallWordAddinOptional($distr, $fileName, $localTools) { + $source = "$distr\data\Add-ins\Word\$fileName" + + $destination1 = "$localTools\optional\" + Write-Host "Copying optional addin $fileName to $destination1" + if (-not (TryCopy $source $destination1)) { + return $false + } + + $destination2 = "$($Env:APPDATA)\Microsoft\Word\STARTUP\" + if (Test-Path -Path "$destination2\$fileName" -PathType Leaf) { + Write-Host "Copying optional addin $fileName to $destination2" + return TryCopy $source $destination2 + } + return $true +} \ No newline at end of file diff --git a/src/ConceptDeploy/Install-PythonLibs.ps1 b/src/ConceptDeploy/Install-PythonLibs.ps1 new file mode 100644 index 0000000..c4d33ef --- /dev/null +++ b/src/ConceptDeploy/Install-PythonLibs.ps1 @@ -0,0 +1,42 @@ +# Install Python libraries used by CIHT CONCEPT tools + +$global:globalLibs = @( + 'pywin32', + 'pandas' +) + +$global:localLibs = @( + 'cctext', + 'vbatopy' +) + +function Install-PythonLibs { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string]$python, + [string]$localDistr = '' + ) + Write-Host "`nUpgrading pip tools..." -ForegroundColor DarkGreen + & $python -m pip install --upgrade pip + InstallGlobalLibs + InstallLocalLibs -libsPath $localDistr +} + +function InstallGlobalLibs { + foreach ($lib in $globalLibs) { + Write-Host "`nInstalling from global repo: $lib" -ForegroundColor DarkGreen + & $python -m pip install -U $lib + } +} + +function InstallLocalLibs([string]$libsPath) { + if ($libsPath -eq '') { + . "$PSScriptRoot\Get-ServerDistr.ps1" + $libsPath = "$(Get-ServerDistr)\src" + } + foreach ($lib in $localLibs) { + Write-Host "`nInstalling from local folder: $libsPath\$lib" -ForegroundColor DarkGreen + & $python -m pip install -U "$libsPath\$lib" + } +} \ No newline at end of file diff --git a/src/ConceptDeploy/Open-DistrManifest.ps1 b/src/ConceptDeploy/Open-DistrManifest.ps1 new file mode 100644 index 0000000..23dcc67 --- /dev/null +++ b/src/ConceptDeploy/Open-DistrManifest.ps1 @@ -0,0 +1,16 @@ +function Open-DistrManifest() { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] $fileName + ) + + Write-Host "Open distribution manifest ... $fileName" + $result = [ordered] @{} + if (-not (Test-Path -Path $fileName -PathType Leaf)) { + return $result + } + $jsonObj = Get-Content -Path $fileName -Raw | ConvertFrom-Json + $jsonObj.PSObject.Properties | ForEach-Object { $result[$_.Name] = $_.Value } + return $result +} \ No newline at end of file diff --git a/src/ConceptDeploy/Push-DLL.ps1 b/src/ConceptDeploy/Push-DLL.ps1 new file mode 100644 index 0000000..3f5fb13 --- /dev/null +++ b/src/ConceptDeploy/Push-DLL.ps1 @@ -0,0 +1,22 @@ +# Push Dynamic library to server and distr +function Push-DLL { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] $dllName, + [Parameter(Mandatory=$true)] + [string] $dllPath, + [bool] $pushServer = $true, + [bool] $pushDistr = $true + ) + + . "$PSScriptRoot\Get-ServerDistr.ps1" + . "$PSScriptRoot\Get-ExchangePath.ps1" + + if ($pushServer) { + Copy-Item -Path "$dllPath\$dllName.dll" -Destination "$(Get-ServerDistr)\dll\" + } + if ($pushDistr) { + Copy-Item -Path "$dllPath\$dllName.dll" -Destination "$(Get-ExchangePath)\ConceptDistr\dll\" + } +} \ No newline at end of file diff --git a/src/ConceptDeploy/Push-PythonPackage.ps1 b/src/ConceptDeploy/Push-PythonPackage.ps1 new file mode 100644 index 0000000..e07007c --- /dev/null +++ b/src/ConceptDeploy/Push-PythonPackage.ps1 @@ -0,0 +1,49 @@ +# Push python module to Concept server and distr +function Push-PythonPackage { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] $packageName, + [Parameter(Mandatory=$true)] + [string] $packageRoot, + [bool] $pushServer = $true, + [bool] $pushDistr = $true + ) + + . "$PSScriptRoot\Get-ServerDistr.ps1" + . "$PSScriptRoot\Get-ExchangePath.ps1" + + if ($pushServer) { + UpdatePythonIn -PackageName $packageName -PackageRoot $packageRoot -ServerRoot "$(Get-ServerDistr)\src" + } + if ($pushDistr) { + UpdatePythonIn -PackageName $packageName -PackageRoot $packageRoot -ServerRoot "$(Get-ExchangePath)\ConceptDistr\src" + } +} + +function UpdatePythonIn($packageName, $packageRoot, $serverRoot) { + . "$PSScriptRoot\Expand-TarGZ.ps1" + + $targz = Get-Childitem -Path "$packageRoot\output\$packageName\*.tar.gz" -Name + if (-not $targz) { + Write-Error "Cannot find unique source distribution in $packageRoot\output\$packageName\" + Exit + } + $targz = "$packageRoot\output\$packageName\$targz" + + $destination = "$serverRoot\$packageName" + if (Test-Path -Path $destination) { + Remove-Item $destination -Recurse -Force + } + + $unpacked = Expand-TarGZ -InFile $targz + if (-not $unpacked) { + Write-Error "Cannot unpack $targz" + Exit + } + + Move-Item -Path "$unpacked\$packageName-*\*" -Destination "$unpacked" + Remove-Item -Path "$unpacked\$packageName-*" + + Move-Item -Path "$unpacked" -Destination "$destination" +} \ No newline at end of file diff --git a/src/ConceptDeploy/Save-DistrManifest.ps1 b/src/ConceptDeploy/Save-DistrManifest.ps1 new file mode 100644 index 0000000..ae522e2 --- /dev/null +++ b/src/ConceptDeploy/Save-DistrManifest.ps1 @@ -0,0 +1,12 @@ +function Save-DistrManifest() { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + $manifestData, + [Parameter(Mandatory=$true)] + [string] $fileName + ) + + Write-Host "Save distribution manifest ... $fileName" + $manifestData | ConvertTo-Json | Out-File -Encoding "UTF8" $fileName +} \ No newline at end of file diff --git a/src/DeployDistributives.ps1 b/src/DeployDistributives.ps1 new file mode 100644 index 0000000..442dea9 --- /dev/null +++ b/src/DeployDistributives.ps1 @@ -0,0 +1,68 @@ +# Prepare and deploy distribution files for standalone distributive +Set-StrictMode -Version 3.0 +Set-Variable -Name PYTHON_DOWNLOAD_VERSION -Value '3.12.2' +Set-Variable -Name MODULE_NAME -Value 'ConceptDeploy' + +function DeployDistributives { + & .\CD_GenerateManifest.ps1 + Import-Module -Name ".\$MODULE_NAME" -Force + + Write-Host "Preparing python environment clone..." + $preparedEnvironment = PrepareEnvironemnt + + Write-Host "Creating python packages archive from local environment..." + $envZip = 'C:\Tools\Python312-venv.zip' + Compress-Archive -Path "$preparedEnvironment\*" -DestinationPath $envZip -Force + Remove-Item $preparedEnvironment -Recurse -Force + + Write-Host "Downloading python $PYTHON_DOWNLOAD_VERSION installer..." + $pythonDistr = DowloadPython $PYTHON_DOWNLOAD_VERSION + + $destination = "$(Get-ExchangePath)\ConceptDistr\distr" + if (-not (Test-Path -Path $destination)) { + New-Item -Path $destination -ItemType Directory -Force | Out-Null + } + + Write-Host "Moving files to destination... $destination" + Copy-Item -Path $pythonDistr -Destination "$destination\" -Force + Copy-Item -Path $envZip -Destination "$destination\" -Force + + Remove-Item $pythonDistr -Force + Remove-Item $envZip -Force +} + +function PrepareEnvironemnt { + $coreFiles = 'activate', 'activate.bat', 'Activate.ps1', 'deactivate.bat', 'python.exe', 'pythonw.exe', 'pythonw_d.exe', 'python_d.exe' + $tools = 'C:\Tools' + $original = "$tools\Python312-venv" + $envClone = "$tools\Python312-cln" + if (-not (Test-Path -Path $original)) { + Exit 1 + } + if (Test-Path -Path $envClone) { + Remove-Item $envClone -Recurse -Force + } + New-Item -Path $envClone -ItemType Directory -Force | Out-Null + Copy-Item -Path "$original\Include" -Destination "$envClone\Include" -Recurse + Copy-Item -Path "$original\Lib" -Destination "$envClone\Lib" -Recurse + Copy-Item -Path "$original\Scripts" -Destination "$envClone\Scripts" -Recurse + + Get-ChildItem $envClone -Recurse | Where-Object{$_.Name.EndsWith('.pyc')} | Remove-Item + Get-ChildItem -Path "$envClone\Scripts\*" -Recurse -Include $coreFiles | Remove-Item + + return $envClone +} + +function DowloadPython($version) { + $pythonUrl = "https://www.python.org/ftp/python/$version/python-$version-amd64.exe" + $pythonTemp = "$Env:TEMP\python-$version-amd64.exe" + try { + (New-Object System.Net.WebClient).DownloadFile($pythonUrl, $pythonTemp) + } catch [System.Net.WebException] { + Write-Error "An exception was caught when trying to download Python: $($_.Exception.Message)" + Exit + } + return $pythonTemp +} + +DeployDistributives \ No newline at end of file diff --git a/src/DeployServer.ps1 b/src/DeployServer.ps1 new file mode 100644 index 0000000..fdb587a --- /dev/null +++ b/src/DeployServer.ps1 @@ -0,0 +1,34 @@ +# Deploy ConceptDeploy module files to server storage +Set-StrictMode -Version 3.0 +Set-Variable -Name MODULE_NAME -Value 'ConceptDeploy' + +& .\CD_GenerateManifest.ps1 +Import-Module -Name ".\$MODULE_NAME" -Force + +$conceptServer = "$(Get-ServerDistr)" +$serverSrc = "$conceptServer\src\$MODULE_NAME" +if (Test-Path -Path $serverSrc) { + Remove-Item $serverSrc -Recurse -Force +} +New-Item -Path "$serverSrc\$MODULE_NAME" -ItemType Directory -Force | Out-Null + +Copy-Item -Path "$PSScriptRoot\CD_Install.ps1" -Destination "$serverSrc\" +Copy-Item -Path "$PSScriptRoot\VERSION" -Destination "$serverSrc\" +Copy-Item -Path "$PSScriptRoot\$MODULE_NAME\*" -Destination "$serverSrc\$MODULE_NAME" -Recurse + +$distr = "$(Get-ExchangePath)\ConceptDistr" +$exchangeSrc = "$distr\src\$MODULE_NAME" +if (Test-Path -Path $exchangeSrc) { + Remove-Item $exchangeSrc -Recurse -Force +} +New-Item -Path $exchangeSrc -ItemType Directory -Force | Out-Null + +Copy-Item -Path "$PSScriptRoot\InstallAllProducts.ps1" -Destination "$distr\" +Copy-Item -Path "$PSScriptRoot\UpdateConceptProducts.ps1" -Destination "$distr\" +Copy-Item -Path "$PSScriptRoot\UninstallConceptProducts.ps1" -Destination "$distr\" +Copy-Item -Path "$PSScriptRoot\install_launcher.bat" -Destination "$distr\install.bat" +Copy-Item -Path "$PSScriptRoot\uninstall.bat" -Destination "$distr\uninstall.bat" +Copy-Item -Path "$PSScriptRoot\install_from_server.bat" -Destination "$distr\install_from_server.bat" +Copy-Item -Path "$PSScriptRoot\install_standalone.bat" -Destination "$distr\install_standalone.bat" +Copy-Item -Path "$PSScriptRoot\..\distr\!README.docx" -Destination "$distr\!README.docx" +Copy-Item -Path "$serverSrc\*" -Destination $exchangeSrc -Recurse \ No newline at end of file diff --git a/src/InstallAllProducts.ps1 b/src/InstallAllProducts.ps1 new file mode 100644 index 0000000..8e9dbab --- /dev/null +++ b/src/InstallAllProducts.ps1 @@ -0,0 +1,344 @@ +# Install Concept products from local distr + +# Setup constants +Set-Variable -Name OFFICE_REGISTRY -Value 'HKCU:\SOFTWARE\Microsoft\Office\16.0' -Option Constant +Set-Variable -Name DOMAINS_REGISTRY -Value 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains' -Option Constant +Set-Variable -Name SERVER_PATH -Value '\\fs1.concept.ru\' -Option Constant +Set-Variable -Name TEMPLATES_PATH -Value "$Env:APPDATA\Microsoft\Шаблоны" -Option Constant +Set-Variable -Name TEMPLATES_NORMAL_PATH -Value "$Env:APPDATA\Microsoft\Templates" -Option Constant + +$word = If (Test-Path "${OFFICE_REGISTRY}\Word") {"${OFFICE_REGISTRY}\Word"} Else {''} +$excel = If (Test-Path "${OFFICE_REGISTRY}\Excel") {"${OFFICE_REGISTRY}\Excel"} Else {''} +$visio = If (Test-Path "${OFFICE_REGISTRY}\Visio") {"${OFFICE_REGISTRY}\Visio"} Else {''} + +function InstallAllProducts { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] $distrPath, + [switch] $skipShortcuts = $false, + [switch] $standalone = $false + ) + + Set-StrictMode -Version 3.0 + + if (-not (Test-Path -Path $distrPath)) { + Write-Error "Distribution path does not exist: $distrPath" + Exit 1 + } + + $InformationPreference="Continue" + + StartLog + + Write-Host "Starting installation procedure for Concept technologies" -ForegroundColor DarkGreen + Write-Host "Sources: $distrPath" + Write-Host "Skip shortcuts: $skipShortcuts`n" + + Write-Host "Dependencies status:" -ForegroundColor DarkGreen + StateSystemStatus + + $driveLetter = MountPathDrive $distrPath + $distr = "${driveLetter}`:" + $localHome = "C:\Tools" + + Write-Host "`nEnsure core is updated..." -ForegroundColor DarkGreen + PrepareCore $distr $distrPath $standalone $localHome + + Write-Host "`nSetup SecureLocation options for $SERVER_PATH" -ForegroundColor DarkGreen + SetupTrustedLocations $SERVER_PATH + + Write-Host "`nInitializing template path for Office applications: $TEMPLATES_PATH" -ForegroundColor DarkGreen + InitializeTemplates $TEMPLATES_PATH + + Write-Host "Update templates at $TEMPLATES_PATH" -ForegroundColor DarkGreen + UpdateTemplates "${distr}\data\Templates" $TEMPLATES_PATH $localHome + Copy-Item -Path "$TEMPLATES_PATH\Normal.dotm" -Destination "${TEMPLATES_NORMAL_PATH}\Normal.dotm" + Copy-Item -Path "$TEMPLATES_PATH\NormalEmail.dotm" -Destination "${TEMPLATES_NORMAL_PATH}\NormalEmail.dotm" + + Write-Host "`nUpdating products..." -ForegroundColor DarkGreen + UpdateProducts $distr $TEMPLATES_PATH $localHome + + if (-not ($skipShortcuts)) { + Write-Host "`nInstalling desktop shortcuts" -ForegroundColor DarkGreen + Copy-Item -Path "${distr}\data\Shortcuts\*" ` + -Destination "$($Env:USERPROFILE)\Desktop\" ` + -Exclude (Get-ChildItem "$($Env:USERPROFILE)\Desktop") -Force -Recurse + } + + Write-Host "`nClean up procedure ..." -ForegroundColor Blue + Remove-PSDrive -Name $driveLetter + + Write-Host "`nInstallation complete" -ForegroundColor Green + + StopLog +} + +function StartLog { + $ErrorActionPreference='SilentlyContinue' + Stop-Transcript | Out-Null + $ErrorActionPreference = "Continue" + + $dateSuffix = (Get-Date -UFormat "%Y%m%d_%I-%M-%S").ToString() + $logFile = "${PSScriptRoot}\logs\${dateSuffix}_$($Env:USERNAME).txt" + Start-Transcript -Path $logFile -IncludeInvocationHeader +} + +function StopLog { + Stop-Transcript +} + +function CheckProcess($processName) { + $app = Get-Process $processName -ErrorAction SilentlyContinue + return -not ($null -eq $app) +} + +function StateSystemStatus() { + if ($word) { + Write-Host "Word running: $(CheckProcess 'WINWORD')" + } else { + Write-Host 'Word not found' -ForegroundColor DarkRed + } + if ($excel) { + Write-Host "Excel running: $(CheckProcess 'EXCEL')" + } else { + Write-Host 'Excel not found' -ForegroundColor DarkRed + } + if ($visio) { + Write-Host "Visio running: $(CheckProcess 'VISIO')" + } else { + Write-Host 'Visio not found' -ForegroundColor DarkRed + } +} + +function MountPathDrive($targetPath) { + Write-Host "`nCreating temporary drive for ${targetPath}" -ForegroundColor DarkGreen + [char[]] $takenDrive = (Get-PSDrive -Name [E-Z]).Name + $driveLetter = ([char[]] (69..90)).Where({ $_ -notin $takenDrive }, 'First')[0] + if (!$driveLetter) { + Write-Error "No available drive letter" + Exit 1 + } + New-PSDrive -Name $driveLetter -PSProvider FileSystem -Root $targetPath -Scope Global | Out-Null + Write-Host "Drive ${driveLetter} mounted" + return $driveLetter +} + +function UpdateTemplates($source, $destination, $localHome) { + Copy-Item -Path "${source}\*" -Destination "$destination\" -Force -Recurse + + $templatesFile = "${localHome}\script\templates_list.txt" + $sourcePrefix = $(Resolve-Path $source).ProviderPath + Write-Host "Save temapltes list to $templatesFile" + Get-ChildItem -Recurse $source | Where-Object { ! $_.PSIsContainer } | Select-Object FullName ` + | ForEach-Object { $_.FullName.Replace("$sourcePrefix\",'') } ` + | Out-File -Encoding "UTF8" $templatesFile +} + +function UpdateProducts($distr, $templates, $localHome) { + $serverManifest = "${distr}\distribution_manifest.json" + $serverJSON = Open-DistrManifest $serverManifest + + $localManifest = "C:\Tools\distribution_manifest.json" + $localJSON = Open-DistrManifest $localManifest + $firstRun = $localJSON.Count -eq 0 + + Write-Host "`n" + foreach ($product in $serverJSON.Keys) { + $serverVersion = [System.Version] $serverJSON.$product + if ($localJSON.Contains($product)) { + $localVersion = [System.Version] $localJSON.$product + } else { + $localVersion = [System.Version] "0.0.0" + } + if ($serverVersion -eq $localVersion) { + Write-Host "Product ${product}: ${serverVersion} ... up to date`n" -ForegroundColor Gray + } elseif (Install-Product $product $distr $templates $localHome) { + Write-Host "Product ${product}: ${serverVersion} ... updated from ${localVersion}`n" -ForegroundColor DarkGreen + $localJSON[$product] = $serverJSON.$product + } else { + Write-Host "Product ${product}: ${serverVersion} ... failed to update from ${localVersion}`n" -ForegroundColor DarkRed + } + } + if ($firstRun) { + Write-Host "`nFirst time run. Enabling default addins" -ForegroundColor DarkGreen + Enable-Product 'WordHelper' $localHome | Out-Null + Enable-Product 'ExcelHelper' $localHome | Out-Null + Enable-Product 'Concept-Reports' $localHome | Out-Null + } + + Save-DistrManifest $localJSON $localManifest +} + +function PrepareCore($distrMount, $distrPath, $standalone, $localHome) { + if (CheckConceptDeploy $distrMount) { + Import-Module -Name ConceptDeploy + Write-Host "ConceptDeploy already installed: $((Get-Module -Name 'ConceptDeploy').Version)" -ForegroundColor Gray + } else { + Write-Host "Installing Powershell library: ConceptDeploy" -ForegroundColor DarkGreen + . "$distrMount\src\ConceptDeploy\CD_Install.ps1" + Import-Module -Name ConceptDeploy + Write-Host "Installed version: ConceptDeploy $((Get-Module -Name 'ConceptDeploy').Version)" + } + + if (-not (Test-Path -Path "${localHome}\script\")) { + New-Item -Path "${localHome}\script\" -ItemType Directory -Force | Out-Null + } + Copy-Item -Path "${distrMount}\UpdateConceptProducts.ps1" -Destination "${localHome}\script\" + Copy-Item -Path "${distrMount}\UninstallConceptProducts.ps1" -Destination "${localHome}\script\" + Copy-Item -Path "${distrMount}\uninstall.bat" -Destination "${localHome}\script\uninstall-concept.bat" + Write-Host "Installed scripts to $localHome\script\" -ForegroundColor Gray + + Write-Host "`nEnsure Python environement is updated..." -ForegroundColor DarkGreen + + $python = '' + if ($standalone) { + if ([Environment]::Is64BitProcess) { + $python = Initialize-Python -PythonDistr "${distrPath}\distr\python-3.12.2-amd64.exe" + } else { + Write-Host "32-bit system do not support Standalone installation mode" -ForegroundColor Red + } + } else { + $python = Initialize-Python + } + + if ($python -eq '') { + Write-Host "Skip python libraries because could not initialize python" -ForegroundColor DarkRed + } elseif ($standalone) { + $pythonFolder = (Get-item $python).Directory.Parent.FullName + Expand-Archive -Path "${distrMount}\distr\Python312-venv.zip" -DestinationPath $pythonFolder -Force + } else { + Install-PythonLibs -python $python -localDistr "${distrPath}\src" + } + + Write-Host "`nUpdate dynamic libraries at ${localHome}" -ForegroundColor DarkGreen + if (-not (Test-Path -Path "${localHome}\dll")) { + New-Item -Path "${localHome}\dll" -ItemType Directory -Force | Out-Null + } + Copy-Item -Path "${distrMount}\dll" -Destination "${localHome}\" -Force -Recurse + + $conceptLocal = Get-ConceptLocal + if (-not (Test-Path -Path "${conceptLocal}\models")) { + New-Item -Path "${conceptLocal}\models" -ItemType Directory -Force | Out-Null + } + if (-not (Test-Path -Path "${localHome}\optional")) { + New-Item -Path "${localHome}\optional" -ItemType Directory -Force | Out-Null + } + if (-not (Test-Path -Path "${localHome}\models")) { + New-Item -Path "${localHome}\models" -ItemType Directory -Force | Out-Null + } +} + +function CheckConceptDeploy($distr) { + $currentModule = Get-Module -ListAvailable ConceptDeploy + if (!$currentModule) { + return $false + } + $newVersion = [System.Version] (Get-Content -Path "${distr}\src\ConceptDeploy\VERSION") + $oldVersion = $currentModule.Version + return $oldVersion -ge $newVersion +} + +function InitializeTemplates($templates) { + $eap = $ErrorActionPreference + $ErrorActionPreference = 'SilentlyContinue' + + if (-not (Test-Path -Path $templates)) { + New-Item -ItemType Directory -Path $templates -Force | Out-Null + Write-Host "Created office templates directory: ${templates}" + } + + if ($word) { + if(-not ( + Get-ItemProperty "${word}\Options" | + ForEach-Object { if ($_ -like '*PersonalTemplates*') { return $true } } + )) { + New-ItemProperty -Path "${word}\Options" -Name 'PersonalTemplates' -Value $templates -PropertyType ExpandString -Force + } else { + Set-ItemProperty -Path "${word}\Options" -Name 'PersonalTemplates' -Value $templates + } + } + if ($excel) { + if(-not ( + Get-ItemProperty "${excel}\Options" | + ForEach-Object { if ($_ -like '*PersonalTemplates*') { return $true } } + )) { + New-ItemProperty -Path "${excel}\Options" -Name 'PersonalTemplates' -Value $templates -PropertyType ExpandString -Force + } else { + Set-ItemProperty -Path "${excel}\Options" -Name 'PersonalTemplates' -Value $templates + } + } + if ($visio) { + if(-not ( + Get-ItemProperty "${visio}\Application" | + ForEach-Object { if ($_ -like '*PersonalTemplates*') { return $true } } + )) { + New-ItemProperty -Path "${visio}\Options" -Name 'PersonalTemplates' -Value $templates -PropertyType ExpandString -Force + } else { + Set-ItemProperty -Path "${visio}\Application" -Name 'PersonalTemplates' -Value $templates + } + } + $ErrorActionPreference = $eap +} + +function SetupTrustedLocations($serverLocation) { + if ($word) { + if (AddTrustedLocation $word $serverLocation) { + Write-Host 'Word ... DONE' + } else { + Write-Host 'Word ... EXISTS' + } + } + if ($excel) { + if (AddTrustedLocation $excel $serverLocation) { + Write-Host 'Excel ... DONE' + } else { + Write-Host 'Excel ... EXISTS' + } + } + if ($visio) { + if (AddTrustedLocation $visio $serverLocation) { + Write-Host 'Visio ... DONE' + } else { + Write-Host 'Visio ... EXISTS' + } + } + $conceptDomain = "${DOMAINS_REGISTRY}\concept.ru\fs1" + if (Test-Path -Path $conceptDomain) { + Write-Host 'Intranet settings ... EXISTS' + } else { + New-Item -Path "${DOMAINS_REGISTRY}\concept.ru" -ItemType Directory | Out-Null + New-Item -Path $conceptDomain -ItemType Directory | Out-Null + New-ItemProperty -Path $conceptDomain -Name 'file' -Value 1 | Out-Null + Write-Host 'Intranet settings ... DONE' + } +} + +function AddTrustedLocation($target, $location) { + $registryPath = "${target}\Security\Trusted Locations" + if (-not(Test-Path -Path $registryPath)) { + if (-not(Test-Path -Path "${target}\Security")){ + New-Item -Path "${target}\Security" -ItemType Directory | Out-Null + } + New-Item -Path $registryPath -ItemType Directory | Out-Null + } + Set-ItemProperty -Path $registryPath -Name 'AllowNetworkLocations' -Value 1 + if ( + Get-ChildItem -Path $registryPath -Recurse | + ForEach-Object { if ($_.GetValue('Path') -eq $location) { return $true } } + ) { + return $false + } else { + $index = 3 + while (Test-Path -Path "${registryPath}\Location${index}") { $index++ } + $folder = "${registryPath}\Location${index}" + New-Item -Path $folder -ItemType Directory | Out-Null + New-ItemProperty -Path $folder -Name 'Path' -Value $location | Out-Null + New-ItemProperty -Path $folder -Name 'Description' -Value 'Сетевой путь КОНЦЕПТ' | Out-Null + New-ItemProperty -Path $folder -Name 'AllowSubfolders' -Value 1 | Out-Null + return $true + } +} + +# InstallAllProducts "D:\test\ConceptDistr" +# SetupTrustedLocations \\fs1.concept.ru\ \ No newline at end of file diff --git a/src/UninstallConceptProducts.ps1 b/src/UninstallConceptProducts.ps1 new file mode 100644 index 0000000..b699844 --- /dev/null +++ b/src/UninstallConceptProducts.ps1 @@ -0,0 +1,127 @@ +# Uninstall Concept products + +function UninstallConceptProducts { + [CmdletBinding()] + Param( + ) + Set-StrictMode -Version 3.0 + $InformationPreference="Continue" + + StartLog + + Write-Host "Starting uninstallation procedure for Concept technologies`n" -ForegroundColor DarkGreen + + PrepareExternalProcesses + + if (-not (Get-Module -ListAvailable ConceptDeploy)) { + Write-Host "Missing ConceptDeploy library" + Exit 1 + } + Import-Module -Name ConceptDeploy + + $localHome = "C:\Tools" + + Write-Host "`nRemoving Concept Products" -ForegroundColor DarkGreen + $localJSON = Open-DistrManifest "$localHome\distribution_manifest.json" + foreach ($product in $localJSON.Keys) { + Disable-Product $product | Out-Null + Write-Host "Product $product has been removed`n" -ForegroundColor Gray + } + TryRemove "$localHome\optional\" + TryRemove "$localHome\models\" + TryRemove "$localHome\distribution_manifest.json" + + Write-Host "`nRemoving user specific files" -ForegroundColor DarkGreen + TryRemove "$(Get-ConceptLocal)\" + + $templates = GetTemplatesFolder + $fileList = Get-Content "$localHome\script\templates_list.txt" + Write-Host "Remove templates at $templates" -ForegroundColor DarkGreen + foreach ($file in $fileList) { + if ($file -ne 'Normal.dotm' -and $file -ne 'NormalEmail.dotm') { + TryRemove "$templates\$file" + } + } + TryRemove "$localHome\script\templates_list.txt" + + Write-Host "`nRemoving core files" + TryRemove "$localHome\script\UpdateConceptProducts.ps1" + TryRemove "$localHome\script\UninstallConceptProducts.ps1" + TryRemove "$localHome\script\uninstall-concept.bat" + TryRemove "$localHome\Python312-venv\" + TryRemove "$localHome\dll\" + + Write-Host "`nRemoving ConceptDeploy library" -ForegroundColor DarkGreen + Remove-Module -Name ConceptDeploy + TryRemove "$env:USERPROFILE\Documents\WindowsPowerShell\modules\ConceptDeploy" + + Write-Host "`nRemoving desktop shortcuts" -ForegroundColor DarkGreen + TryRemove "$($Env:USERPROFILE)\Desktop\Everything - поиск по имени.lnk" + TryRemove "$($Env:USERPROFILE)\Desktop\DocFetcher - поиск по содержимому.lnk" + TryRemove "$($Env:USERPROFILE)\Desktop\Double Commander.lnk" + + Write-Host "`nUninstallation complete" -ForegroundColor Green + + StopLog +} + +function StartLog { + $ErrorActionPreference="SilentlyContinue" + Stop-Transcript | Out-Null + $ErrorActionPreference = "Continue" + + $dateSuffix = (Get-Date -UFormat "%Y%m%d_%I-%M-%S").ToString() + $logFile = "$PSScriptRoot\logs\$($dateSuffix)_CP-uninstall_$($Env:USERNAME).txt" + Start-Transcript -Path $logFile -IncludeInvocationHeader +} + +function StopLog { + Stop-Transcript +} + +function CheckProcess($processName) { + $app = Get-Process $processName -ErrorAction SilentlyContinue + return -not ($null -eq $app) +} + +function TryRemove($path) { + if (Test-Path -Path $path) { + Write-Host "Removing $path..." -ForegroundColor Gray + Remove-Item -Path $path -Recurse -Force + } else { + Write-Host "Failed to locate $path..." -ForegroundColor Red + } +} + +function PrepareExternalProcesses { + Write-Host "Close blocking applications..." -ForegroundColor DarkGreen + if ($(CheckProcess WINWORD)) { + Stop-Process -Name WINWORD -Force + } + if ($(CheckProcess EXCEL)) { + Stop-Process -Name EXCEL -Force + } + if ($(CheckProcess VISIO)) { + Stop-Process -Name VISIO -Force + } + if ($(CheckProcess Exteor)) { + Stop-Process -Name Exteor -Force + } + Write-Host "Word running: $(CheckProcess WINWORD)" + Write-Host "Excel running: $(CheckProcess EXCEL)" + Write-Host "Visio running: $(CheckProcess VISIO)" + Write-Host "Exteor running: $(CheckProcess Exteor)" +} + +function GetTemplatesFolder { + $officeRK = 'HKCU:\SOFTWARE\Microsoft\Office\16.0' + $defaultTemplates = "$($Env:APPDATA)\Microsoft\Шаблоны" + + $templates = Get-ItemPropertyValue -Path "$officeRK\Word\Options" -Name 'PersonalTemplates' -ErrorAction 'SilentlyContinue' + if (!$templates -or [string]::IsNullOrWhiteSpace($templates)) { + $templates = $defaultTemplates + } + return $templates +} + +# UninstallConceptProducts \ No newline at end of file diff --git a/src/UpdateConceptProducts.ps1 b/src/UpdateConceptProducts.ps1 new file mode 100644 index 0000000..2fd3772 --- /dev/null +++ b/src/UpdateConceptProducts.ps1 @@ -0,0 +1,162 @@ +# Install Concept products from local distr + +function UpdateConceptProducts { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [string] $distrPath, + [string[]] $products = $(, 'all') + ) + + Set-StrictMode -Version 3.0 + + if ($products.Count -lt 1) { + Write-Host 'Missing products list' + Exit 1 + } + + StartLog + + Write-Host "Starting update procedure for Concept technologies" -ForegroundColor DarkGreen + Write-Host "Sources: $distrPath" + Write-Host "Requested products: $products" + + $updates = New-Object System.Collections.Generic.List[System.Object] + $enable_products = New-Object System.Collections.Generic.List[System.Object] + $disable_products = New-Object System.Collections.Generic.List[System.Object] + if ($products[0] -eq 'all') { + $updates.Add('all') + } else { + foreach ($p in $products) { + if ($p.EndsWith('-') -or $p.EndsWith('+')) { + $product_id = $p.Substring(0, $p.Length - 1) + } else { + $product_id = $p + } + if ($p.EndsWith('-')) { + $disable_products.Add($product_id) + } else { + $updates.Add($product_id) + $enable_products.Add($product_id) + } + } + } + Write-Host "Check updates: $updates" + Write-Host "Enable products: $enable_products" + Write-Host "Disable products: $disable_products" + + if (Test-Path -Path "filesystem::$distrPath\distribution_manifest.json") { + Write-Host "Check path $distrPath\distribution_manifest.json ... ACCESS GRANTED" -ForegroundColor DarkGreen + UpdateProducts $distrPath $updates + + } else { + Write-Host "Check path $distrPath\distribution_manifest.json ... NO ACCESS" -ForegroundColor DarkRed + Write-Host "Skipping product updates..." -ForegroundColor DarkRed + } + + $tools = 'C:\Tools' + foreach ($product in $enable_products) { + if (Enable-Product $product $tools) { + Write-Host "Product $($product): enabled`n" -ForegroundColor DarkGreen + } else { + Write-Host "Product $($product): failed to enable`n" -ForegroundColor DarkRed + } + } + + $tools = 'C:\Tools' + foreach ($product in $disable_products) { + if (Disable-Product $product) { + Write-Host "Product $($product): disabled`n" -ForegroundColor DarkGreen + } else { + Write-Host "Product $($product): failed to disable`n" -ForegroundColor DarkRed + } + } + + Write-Host "`nUpdate complete" -ForegroundColor Green + + StopLog +} + +function StartLog { + $ErrorActionPreference="SilentlyContinue" + Stop-Transcript | Out-Null + $ErrorActionPreference = "Continue" + + $dateSuffix = (Get-Date -UFormat "%Y%m%d_%I-%M-%S").ToString() + $logFile = "$PSScriptRoot\logs\$($dateSuffix)_CP-update_$($Env:USERNAME).txt" + Start-Transcript -Path $logFile -IncludeInvocationHeader +} + +function StopLog { + Stop-Transcript +} + +function UpdateProducts($distrPath, $products) { + $driveLetter = MountPathDrive $distrPath + $distr = "$driveLetter`:" + + if (-not(CheckConceptDeploy $distr)) { + Write-Host "Installing Powershell library: ConceptDeploy" -ForegroundColor DarkGreen + . "$driveLetter\src\ConceptDeploy\CD_Install.ps1" + } + Import-Module -Name ConceptDeploy + Write-Host "Using ConceptDeploy: $((Get-Module -Name 'ConceptDeploy').Version)" -ForegroundColor Gray + + if ($products.Count -ne 0) { + $serverManifest = "$distr\distribution_manifest.json" + $serverJSON = Open-DistrManifest $serverManifest + + $localManifest = "C:\Tools\distribution_manifest.json" + $localJSON = Open-DistrManifest $localManifest + + if ($products[0] -eq 'all') { + $products = $serverJSON.Keys + } + foreach ($product in $products) { + $serverVersion = [System.Version] $serverJSON.$product + if ($localJSON.Contains($product)) { + $localVersion = [System.Version] $localJSON.$product + } else { + $localVersion = [System.Version] "0.0.0" + } + if ($serverVersion -eq $localVersion) { + Write-Host "Product $($product): $serverVersion ... up to date`n" -ForegroundColor Gray + } elseif (Install-Product $product $distr $templates $localTools) { + Write-Host "Product $($product): $serverVersion ... updated from $localVersion`n" -ForegroundColor DarkGreen + $localJSON[$product] = $serverJSON.$product + } else { + Write-Host "Product $($product): $serverVersion ... failed to update from $localVersion`n" -ForegroundColor DarkRed + } + } + Save-DistrManifest $localJSON $localManifest + } + + Write-Host "`nUnmount $driveLetter" -ForegroundColor DarkGreen + Remove-PSDrive -Name $driveLetter +} + +function MountPathDrive($targetPath) { + Write-Host "`nCreating temporary drive for $targetPath" -ForegroundColor DarkGreen + [char[]] $takenDrive = (Get-PSDrive -Name [E-Z]).Name + $driveLetter = ([char[]] (69..90)).Where({ $_ -notin $takenDrive }, 'First')[0] + if (!$driveLetter) { + Write-Error "No available drive letter" + Exit 1 + } + New-PSDrive -Name $driveLetter -PSProvider FileSystem -Root $targetPath -Scope Global | Out-Null + Write-Host "Drive $driveLetter mounted" + return $driveLetter +} + +function CheckConceptDeploy($distr) { + $currentModule = Get-Module -ListAvailable ConceptDeploy + if (!$currentModule) { + return $false + } + $newVersion = [System.Version] (Get-Content -Path "$distr\src\ConceptDeploy\VERSION") + $oldVersion = $currentModule.Version + return $oldVersion -ge $newVersion +} + + +# UpdateConceptProducts -DistrPath '\\fs1.concept.ru\Exchange\ConceptDistr' 'Concept-Maket-', 'Concept-Mining+' \ No newline at end of file diff --git a/src/VERSION b/src/VERSION new file mode 100644 index 0000000..13d683c --- /dev/null +++ b/src/VERSION @@ -0,0 +1 @@ +3.0.1 \ No newline at end of file diff --git a/src/install_from_server.bat b/src/install_from_server.bat new file mode 100644 index 0000000..f4e25bd --- /dev/null +++ b/src/install_from_server.bat @@ -0,0 +1,7 @@ +@echo off + +@pushd %~dp0 +PowerShell -NoProfile -ExecutionPolicy Bypass -Command ". "%cd%\InstallAllProducts.ps1"; InstallAllProducts \\fs1.concept.ru\Exchange\ConceptDistr;" +@popd + +pause \ No newline at end of file diff --git a/src/install_launcher.bat b/src/install_launcher.bat new file mode 100644 index 0000000..f1d9c3e --- /dev/null +++ b/src/install_launcher.bat @@ -0,0 +1,7 @@ +@echo off + +@pushd %~dp0 +PowerShell -NoProfile -ExecutionPolicy Bypass -Command ". "%cd%\InstallAllProducts.ps1"; InstallAllProducts %cd%;" +@popd + +pause \ No newline at end of file diff --git a/src/install_standalone.bat b/src/install_standalone.bat new file mode 100644 index 0000000..30a4a55 --- /dev/null +++ b/src/install_standalone.bat @@ -0,0 +1,7 @@ +@echo off + +@pushd %~dp0 +PowerShell -NoProfile -ExecutionPolicy Bypass -Command ". "%cd%\InstallAllProducts.ps1"; InstallAllProducts %cd% -Standalone -SkipShortcuts;" +@popd + +pause \ No newline at end of file diff --git a/src/uninstall.bat b/src/uninstall.bat new file mode 100644 index 0000000..d5c6fd8 --- /dev/null +++ b/src/uninstall.bat @@ -0,0 +1,7 @@ +@echo off + +@pushd %~dp0 +PowerShell -NoProfile -ExecutionPolicy Bypass -Command ". "%cd%\UninstallConceptProducts.ps1"; UninstallConceptProducts;" +@popd + +pause \ No newline at end of file