diff --git a/.github/workflows/build_msi_installer.yml b/.github/workflows/build_msi_installer.yml new file mode 100644 index 00000000000..7df90b09a45 --- /dev/null +++ b/.github/workflows/build_msi_installer.yml @@ -0,0 +1,103 @@ +name: Build and Package MSI + +on: + workflow_dispatch: + inputs: + UploadAsLatest: + type: string + default: "False" + required: false + description: 'Also upload the msi installer to storage account as latest' + +jobs: + build_msi_installer: + runs-on: windows-latest + name: Build Windows MSI + + steps: + - name: Check input parameters + run: | + echo "UploadAsLatest: ${{ inputs.UploadAsLatest }}" + + - name: Checkout Repo + uses: actions/checkout@v3 + with: + submodules: true + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + + - name: Install WIX Toolset + shell: pwsh + working-directory: ${{ github.workspace }}/scripts/installer/windows + run: | + Invoke-WebRequest -Uri 'https://azurecliprod.blob.core.windows.net/msi/wix310-binaries-mirror.zip' -OutFile 'wix-archive.zip' + Expand-Archive -Path 'wix-archive.zip' -DestinationPath 'wix' + Remove-Item -Path 'wix-archive.zip' + + + - name: Install Python and dependencies + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install promptflow dependencies + run: | + python -m pip install --upgrade pip + pip install promptflow[azure,executable] promptflow-tools + shell: pwsh + + - name: Get promptflow version + id: get-version + run: | + $version=$(python -c "import promptflow; print(promptflow.__version__)") + echo "::set-output name=version::$version" + shell: pwsh + + - name: Build Pyinstaller project + working-directory: ${{ github.workspace }}/scripts/installer/windows/scripts + run: | + echo 'Version from promptflow: ${{ steps.get-version.outputs.version }}' + $text = Get-Content "version_info.txt" -Raw + + $versionString = '${{ steps.get-version.outputs.version }}' + $versionArray = $versionString.Split('.') + 0 + $versionTuple = [string]::Join(', ', $versionArray) + $text = $text -replace '\$\((env\.FILE_VERSION)\)', $versionTuple + + $text = $text -replace '\$\((env\.CLI_VERSION)\)', '${{ steps.get-version.outputs.version }}' + $text | Out-File -FilePath "version_info.txt" -Encoding utf8 + pyinstaller promptflow.spec + shell: pwsh + + - name: Build WIX project + working-directory: ${{ github.workspace }}/scripts/installer/windows + run: | + $text = Get-Content "promptflow.wixproj" -Raw + $text = $text -replace '\$\((env\.CLI_VERSION)\)', '${{ steps.get-version.outputs.version }}' + $text | Out-File -FilePath "promptflow.wixproj" -Encoding utf8 + + $text = Get-Content "product.wxs" -Raw + $text = $text -replace '\$\((env\.CLI_VERSION)\)', '${{ steps.get-version.outputs.version }}' + $text | Out-File -FilePath "product.wxs" -Encoding utf8 + + msbuild /t:rebuild /p:Configuration=Release /p:Platform=x64 promptflow.wixproj + shell: pwsh + + - name: Azure Login + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Upload to Azure Storage + run: | + $msi_files = Get-ChildItem -Path 'scripts/installer/windows/out/' -Filter *.msi + foreach ($msi_file in $msi_files) { + if ($env:INPUT_UPLOADASLATEST -ieq 'True') { + az storage blob upload --account-name promptflowartifact --container-name msi-installer --file "scripts/installer/windows/out/$($msi_file.Name)" --name "promptflow.msi" --overwrite + az storage blob copy start --account-name promptflowartifact --destination-container msi-installer --destination-blob "$($msi_file.Name)" --source-container msi-installer --source-blob "promptflow.msi" + } else { + az storage blob upload --account-name promptflowartifact --container-name msi-installer --file "scripts/installer/windows/out/$($msi_file.Name)" --name "$($msi_file.Name)" --overwrite + } + } + shell: pwsh \ No newline at end of file diff --git a/scripts/installer/windows/README.md b/scripts/installer/windows/README.md index 37df1fce8e7..95657c1c821 100644 --- a/scripts/installer/windows/README.md +++ b/scripts/installer/windows/README.md @@ -1,10 +1,13 @@ -Building the Windows MSI Installer -======================== +# Building the Windows MSI Installer This document provides instructions on creating the MSI installer. -Prerequisites -------------- +## Option1: Building with Github Actions +Trigger the [workflow](https://github.com/microsoft/promptflow/actions/workflows/build_msi_installer.yml) manually. + + +## Option2: Local Building +### Prerequisites 1. Turn on the '.NET Framework 3.5' Windows Feature (required for WIX Toolset). 2. Install 'Microsoft Build Tools 2015'. @@ -20,12 +23,11 @@ Prerequisites - `pip install promptflow[azure,executable] promptflow-tools` -Building --------- -1. `cd scripts/installer/windows/scripts` and run `pyinstaller promptflow.spec`. -2. `cd scripts/installer/windows` and Run `msbuild /t:rebuild /p:Configuration=Release /p:Platform=x64 promptflow.wixproj`. -3. The unsigned MSI will be in the `scripts/installer/windows/out` folder. +### Building +1. Update the version number `$(env.CLI_VERSION)` and `$(env.FILE_VERSION)` in `product.wxs`, `promptflow.wixproj` and `version_info.txt`. +2. `cd scripts/installer/windows/scripts` and run `pyinstaller promptflow.spec`. +3. `cd scripts/installer/windows` and Run `msbuild /t:rebuild /p:Configuration=Release /p:Platform=x64 promptflow.wixproj`. +4. The unsigned MSI will be in the `scripts/installer/windows/out` folder. -Notes --------- +## Notes - If you encounter "Access is denied" error when running promptflow. Please follow the [link](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-deployment-implement?view=o365-worldwide#customize-attack-surface-reduction-rules) to add the executable to the Windows Defender Attack Surface Reduction (ASR) rule. \ No newline at end of file diff --git a/scripts/installer/windows/product.wxs b/scripts/installer/windows/product.wxs index 184d9c87b12..9b968f88339 100644 --- a/scripts/installer/windows/product.wxs +++ b/scripts/installer/windows/product.wxs @@ -1,14 +1,13 @@ - + - + - + - @@ -132,11 +131,6 @@ Value="$(var.ProductVersion)" KeyPath="yes"/> - - - - - diff --git a/scripts/installer/windows/promptflow.wixproj b/scripts/installer/windows/promptflow.wixproj index 4bc976d1210..bbbe6a809eb 100644 --- a/scripts/installer/windows/promptflow.wixproj +++ b/scripts/installer/windows/promptflow.wixproj @@ -7,7 +7,7 @@ 3.10 04ff6707-750d-4474-89b3-7922c84721be 2.0 - promptflow + promptflow-$(env.CLI_VERSION) Package $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets @@ -61,7 +61,6 @@ WixUtilExtension - diff --git a/scripts/installer/windows/scripts/pfs.py b/scripts/installer/windows/scripts/pfs.py index e5af4b70943..724fc271fc2 100644 --- a/scripts/installer/windows/scripts/pfs.py +++ b/scripts/installer/windows/scripts/pfs.py @@ -2,55 +2,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # --------------------------------------------------------- from promptflow._sdk._service.entry import main -import sys - -import win32serviceutil # ServiceFramework and commandline helper -import win32service # Events -import servicemanager # Simple setup and logging - - -class MyService: - """Silly little application stub""" - def stop(self): - """Stop the service""" - self.running = False - - def run(self): - """Main service loop. This is where work is done!""" - self.running = True - while self.running: - main() # Important work - servicemanager.LogInfoMsg("Service running...") - - -class MyServiceFramework(win32serviceutil.ServiceFramework): - - _svc_name_ = 'PromptflowService' - _svc_display_name_ = 'Promptflow Service' - - def SvcStop(self): - """Stop the service""" - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - self.service_impl.stop() - self.ReportServiceStatus(win32service.SERVICE_STOPPED) - - def SvcDoRun(self): - """Start the service; does not return until stopped""" - self.ReportServiceStatus(win32service.SERVICE_START_PENDING) - self.service_impl = MyService() - self.ReportServiceStatus(win32service.SERVICE_RUNNING) - # Run the service - self.service_impl.run() - - -def init(): - if len(sys.argv) == 1: - servicemanager.Initialize() - servicemanager.PrepareToHostSingle(MyServiceFramework) - servicemanager.StartServiceCtrlDispatcher() - else: - win32serviceutil.HandleCommandLine(MyServiceFramework) if __name__ == '__main__': - init() + main() diff --git a/scripts/installer/windows/scripts/version_info.txt b/scripts/installer/windows/scripts/version_info.txt index 19132294f54..12b4ada8875 100644 --- a/scripts/installer/windows/scripts/version_info.txt +++ b/scripts/installer/windows/scripts/version_info.txt @@ -1,4 +1,4 @@ -# UTF-8 +# UTF-8 # # For more details about fixed file info 'ffi' see: # http://msdn.microsoft.com/en-us/library/ms646997.aspx @@ -6,7 +6,7 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. - filevers=(1, 0, 0, 0), + filevers=($(env.FILE_VERSION)), prodvers=(1, 0, 0, 0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, @@ -35,8 +35,8 @@ VSVersionInfo( StringStruct('InternalName', 'setup'), StringStruct('LegalCopyright', 'Copyright (c) Microsoft Corporation. All rights reserved.'), StringStruct('ProductName', 'Microsoft prompt flow'), - StringStruct('ProductVersion', '1.0.0.0')]) - ]), + StringStruct('ProductVersion', '$(env.CLI_VERSION)')]) + ]), VarFileInfo([VarStruct('Translation', [1033, 1252])]) ] ) \ No newline at end of file