├── .gitignore ├── Install Using GPO Tools.Package.ps1 ├── Install Using GPO Tools.pssproj ├── Install Using GPO Tools.sln ├── Install Using GPO Tools.suo ├── Install Using GPO Tools.v12.suo ├── Install-Application.ps1 ├── Install-Update.ps1 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Package/ 2 | Bin/ 3 | Obj/ -------------------------------------------------------------------------------- /Install Using GPO Tools.Package.ps1: -------------------------------------------------------------------------------- 1 | $Files = @( 2 | @{ Filename = 'Install-Application.ps1'; }; 3 | @{ Filename = 'Install-Update.ps1' }; 4 | @{ Filename = 'README.md' }; 5 | ) 6 | 7 | ########################################################################################################################################## 8 | # Support Functions 9 | ########################################################################################################################################## 10 | Function InitZip 11 | { 12 | # If PS is version 4 or less then we require the PSCX Module to unzip/zip files 13 | If ($PSVersionTable.PSVersion.Major -lt 5) { 14 | # Is the PSCX Module Available? 15 | If ( (Get-Module -ListAvailable PSCX | Measure-Object).Count -eq 0) { 16 | Throw "PSCX Module is not available. Please download it from http://pscx.codeplex.com/" 17 | } # If 18 | Import-Module PSCX 19 | } # If 20 | } # Function InitZip 21 | ########################################################################################################################################## 22 | 23 | ########################################################################################################################################## 24 | Function UnzipFile ([String]$ZipFileName,[String]$DestinationPath) 25 | { 26 | If ($PSVersionTable.PSVersion.Major -lt 5) { 27 | Expand-Archive -Path $ZipFileName -OutputPath $DestinationPath 28 | } Else { 29 | Expand-Archive -Path $ZipFileName -DestinationPath $DestinationPath -Force 30 | } # If 31 | } # Function UnzipFile 32 | ########################################################################################################################################## 33 | 34 | ########################################################################################################################################## 35 | Function ZipFolder ([String]$ZipFileName,[String]$SourcePath) 36 | { 37 | If ($PSVersionTable.PSVersion.Major -lt 5) { 38 | Get-ChildItem -Path $SourcePath -Recurse | Write-Zip -IncludeEmptyDirectories -OutputPath $ZipFileName -EntryPathRoot $SourcePath -Level 9 39 | } Else { 40 | Compress-Archive -DestinationPath $ZipFileName -Path "$SourcePath\*" -CompressionLevel Optimal 41 | } # If 42 | } # Function ZipFolder 43 | ########################################################################################################################################## 44 | 45 | ########################################################################################################################################## 46 | Function Package-Module 47 | { 48 | <# 49 | .SYNOPSIS 50 | Packages the files required for distributing a module. 51 | 52 | .DESCRIPTION 53 | All this function does is zip up the files required to be distributed with the a module. 54 | 55 | If PS 4 is used then this function requires the PSCX module to be available and installed on this computer. 56 | 57 | .LINK 58 | http://pscx.codeplex.com/ 59 | #> 60 | Param ( 61 | [String]$Name 62 | ) # Params 63 | 64 | Begin { 65 | # Initialize the zip functions 66 | InitZip 67 | 68 | [String]$TempPath = "$Env:TEMP\Package\" 69 | New-Item -Path $TempPath -ItemType 'Directory' -Force | Out-Null 70 | New-Item -Path "$TempPath\$Name" -ItemType 'Directory' -Force | Out-Null 71 | } # Begin 72 | 73 | Process { 74 | Foreach ($File In $Files) { 75 | If ($File.Filename.Substring($File.Filename.Length-1,1) -eq '\') { 76 | New-Item -Path "$TempPath\$Name\$($File.Filename)" -ItemType Directory -Force | Out-Null 77 | } Else { 78 | Copy-Item -Path "$PSScriptRoot\$($File.Filename)" -Destination "$TempPath\$Name\$($File.Filename)" -Force 79 | } # If 80 | } # Foreach 81 | New-Item -Path "$PSScriptRoot\Package" -ItemType Directory -Force 82 | If (Test-Path -Path "$PSScriptRoot\Package\$Name.zip" ) { Remove-Item -Path "$PSScriptRoot\Package\$Name.zip" -Force | Out-Null } 83 | ZipFolder -ZipFileName "$PSScriptRoot\Package\$Name.zip" -SourcePath $TempPath 84 | } # Process 85 | 86 | End { 87 | Remove-Item -Path $TempPath -Recurse -Force 88 | } # End 89 | } # Function Package-Module 90 | ########################################################################################################################################## 91 | 92 | Package-Module -Name 'InstallUsingGPOTools' -------------------------------------------------------------------------------- /Install Using GPO Tools.pssproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 7 | Exe 8 | MyApplication 9 | MyApplication 10 | Install Using GPO Tools 11 | 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\Debug\ 18 | DEBUG;TRACE 19 | prompt 20 | 4 21 | 22 | 23 | pdbonly 24 | true 25 | bin\Release\ 26 | TRACE 27 | prompt 28 | 4 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Install Using GPO Tools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "Install Using GPO Tools", "Install Using GPO Tools.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Install Using GPO Tools.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlagueHO/InstallUsingGPOTools/5c7e5bfb2df82a90bc1fc341ec95bb647627aca8/Install Using GPO Tools.suo -------------------------------------------------------------------------------- /Install Using GPO Tools.v12.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlagueHO/InstallUsingGPOTools/5c7e5bfb2df82a90bc1fc341ec95bb647627aca8/Install Using GPO Tools.v12.suo -------------------------------------------------------------------------------- /Install-Application.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Installs an Application from a local or network media source if a registry key/value is not set. 4 | 5 | .DESCRIPTION 6 | Installs an Application from a specified media source by executing the setup installer (.EXE) file. 7 | 8 | A registry key must also be provided to check for to identify if the application is already installed. Optionally a registry value in the registry key can also be checked for. 9 | 10 | This script would normally be used with the Windows Server 2012 GPO PowerShell Start up Script feature to install a specific application. 11 | 12 | .PARAMETER InstallerPath 13 | The location of the installation application executable. Can be a local or network path. 14 | 15 | .PARAMETER RegistryKey 16 | The registry key to check for. If the registry key does not exist then the application will be installed. 17 | 18 | .PARAMETER InstallerParameters 19 | An optional string parameter containing any installation parameters that should be passed to the installation executable, usually to force an unattended and silent installation. 20 | 21 | .PARAMETER RegistryName 22 | An optional registry value to check for in the registry key. If the registry key does not contain the registry value with this name then the application will be installed. 23 | 24 | .PARAMETER RegistryValue 25 | An optional registry value that the registry name in the key must equal. If the registry name value does not match this parameter then the application will be installed. 26 | 27 | .PARAMETER LogPath 28 | Optional parameter specifying where the installation log file should be written to. If not specified, an installation log file will not be written. 29 | The installation log file will be named with the name of the computer being installed to. 30 | 31 | .EXAMPLE 32 | Install Notepad++ 6.7.8.2 without creating a logfile: 33 | Install-Application -InstallerPath '\\server\Software$\Notepad++\npp.6.7.8.2.Installer.exe' -RegistryKey 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Notepad++' -RegistryName 'DisplayVersion' -RegistryValue '6.7.8.2' -InstallerParameters '/S' 34 | 35 | Install Notepad++ 6.7.8.2 creating log files for each machine it is installed on in \\Server\Software$\logfiles\ folder: 36 | Install-Application -InstallerPath '\\server\Software$\Notepad++\npp.6.7.8.2.Installer.exe' -RegistryKey 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Notepad++' -RegistryName 'DisplayVersion' -RegistryValue '6.7.8.2' -InstallerParameters '/S' -LogPath \\Server\Software$\logfiles\ 37 | 38 | .OUTPUTS 39 | 40 | .NOTES 41 | #> 42 | 43 | # AUTHOR 44 | # Daniel Scott-Raynsford 45 | # http://dscottraynsford.wordpress.com/ 46 | # 47 | # VERSION 48 | # 1.0 2015-06-30 Daniel Scott-Raynsford Incomplete Version 49 | # 50 | 51 | [CmdLetBinding( 52 | SupportsShouldProcess=$true 53 | )] 54 | 55 | param( 56 | [String] 57 | [Parameter( 58 | Position=1, 59 | Mandatory=$true 60 | )] 61 | [ValidateScript({ ($_ -ne '') -and ( Test-Path $_ ) })] 62 | $InstallerPath, 63 | 64 | [String] 65 | [Parameter( 66 | Position=2, 67 | Mandatory=$true 68 | )] 69 | [ValidateNotNullOrEmpty()] 70 | $RegistryKey='', 71 | 72 | [String] 73 | [Parameter( 74 | Position=3 75 | )] 76 | $InstallerParameters='', 77 | 78 | [String] 79 | [Parameter( 80 | Position=4 81 | )] 82 | [ValidateNotNullOrEmpty()] 83 | $RegistryName='', 84 | 85 | [String] 86 | [Parameter( 87 | Position=5 88 | )] 89 | [ValidateNotNullOrEmpty()] 90 | $RegistryValue='', 91 | 92 | [String] 93 | [Parameter( 94 | Position=6 95 | )] 96 | [ValidateScript({ ( $_ -ne '' ) -and ( Test-Path $_ ) })] 97 | $LogPath 98 | ) # Param 99 | 100 | Function Add-LogEntry ( [String]$Path ,[String]$Message) 101 | { 102 | Write-Verbose -Message $Message 103 | # Only write log entry if a path was specified 104 | If ( $Path -ne '' ) { 105 | Add-Content -Path $Path -Value "$(Get-Date): $Message" 106 | } # ( $Path -ne '' ) 107 | } # Function Add-LogEntry 108 | 109 | # If a Log Path was specified get up a log file name to write to. 110 | If (($LogPath -eq '') -or ($LogPath -eq $null)) { 111 | [String]$LogFile = '' 112 | } else { 113 | [String]$LogFile = Join-Path -Path $LogPath -ChildPath "$($ENV:computername)_$([System.IO.Path]::GetFileNameWithoutExtension($InstallerPath)).txt" 114 | } # ($LogPath -eq '') 115 | 116 | # Perform registry checks to see if app is already installed 117 | [Boolean]$Installed = $False 118 | If ( Test-Path -Path $RegistryKey ) { 119 | Write-Verbose -Message "Registry Key $RegistryKey found." 120 | If ( ($RegistryName -ne $null) -and ($RegistryName -ne '') ) { 121 | Try { 122 | # Can a Registry Key Property with the name RegistryName be found? If no, then an error will be thrown and 123 | $RegProperty = Get-ItemProperty -Path $RegistryKey -Name $RegistryName 124 | Write-Verbose -Message "Registry Item Property $RegistryName found with value $($RegProperty.$RegistryName)." 125 | # Does the Registry Key Property Value match registry Value? 126 | If ($RegProperty.$RegistryName -eq $RegistryValue) { 127 | # Yes, app is installed. 128 | [Boolean]$Installed = $True 129 | } 130 | } Catch { 131 | Write-Verbose -Message "Registry Item Property $RegistryName was not found." 132 | # Registry Key Property not found so not installed. 133 | } # Try 134 | } Else { 135 | # Only Registry Key was provided for check so app is installed. 136 | [Boolean]$Installed = $True 137 | } # ( ($RegistryName -eq $null) -or ($RegistryName -eq '') ) 138 | } # ( Test-Path -Path $RegistryKey ) 139 | 140 | # This application is not installed - so install it. 141 | If (-not $Installed) { 142 | [String]$Command="$InstallerPath $InstallerParameters" 143 | 144 | Add-LogEntry -Path $LogFile -Message "Install Application using $Command started." 145 | If ($PSCmdlet.ShouldProcess("Install Application using $Command started")) { 146 | # Call the product Install. 147 | & cmd.exe /c "$Command" 148 | [Int]$ErrorCode = $LASTEXITCODE 149 | } # ShouldProcess 150 | Switch ($ErrorCode) { 151 | 0 { Add-LogEntry -Path $LogFile -Message "Install $Type using $Command completed successfully." } 152 | 1641 { Add-LogEntry -Path $LogFile -Message "Install $Type using $Command completed successfully and computer is rebooting." } 153 | default { Add-LogEntry -Path $LogFile -Message "Install $Type using $Command failed with error code $ErrorCode." } 154 | } # ($ErrorCode) 155 | } Else { 156 | Write-Verbose -Message "Application is already installed." 157 | } # (-not $Installed) 158 | -------------------------------------------------------------------------------- /Install-Update.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Installs an Windows QFE Update from a local or network media source if not already installed. 4 | 5 | .DESCRIPTION 6 | Installs a Windows QFE Update from a specified media source by executing the update installer (.EXE) or Microsoft Update (.MSU) file. 7 | 8 | This script would normally be used with the Windows Server 2012 GPO PowerShell Start up Script feature to install a specific application or update. 9 | 10 | Normally WSUS would be used to distribute and install QFE updates, but some updates are not always available via this method (Windows Management Framework 3.0 and above for example). 11 | 12 | .PARAMETER InstallerPath 13 | The location of the update executable or MSU. Can be a local or network path. If an MSU file is specified then it will be automatically be installed by the WUSA.EXE with the appropraite parameters to ensure unattended installation. If an EXE file is specified then the appropriate quiet mode parameters are used. 14 | 15 | .PARAMETER KBID 16 | This is the KB ID for this Windows QFE Update. It must match that of the update specified in the Installer path because it is used to detect if this update has already been installed. The KBID is usually found in the update filename. 17 | 18 | .PARAMETER LogPath 19 | Optional parameter specifying where the installation log file should be written to. If not specified, an installation log file will not be written. 20 | The installation log file will be named with the name of the computer being installed to. 21 | 22 | .EXAMPLE 23 | To install the Windows Management Framework 5.0 April 2015 update with no log file creation: 24 | Install-Update -InstallerPath \\Server\Software$\Updates\WindowsBlue-KB3055381-x64.msu -KBID KB3055381 25 | 26 | .EXAMPLE 27 | To install the Windows Management Framework 5.0 April 2015 update creating log files for each machine it is installed on in \\Server\Logfiles$\ folder: 28 | Install-Update -InstallerPath \\Server\Software$\Updates\WindowsBlue-KB3055381-x64.msu -KBID KB3055381 -LogPath \\Server\Logfiles$\ 29 | 30 | .OUTPUTS 31 | 32 | .NOTES 33 | #> 34 | 35 | # AUTHOR 36 | # Daniel Scott-Raynsford 37 | # http://dscottraynsford.wordpress.com/ 38 | # 39 | # VERSION 40 | # 1.0 2015-06-30 Daniel Scott-Raynsford Initial Version 41 | # 42 | 43 | [CmdLetBinding( 44 | SupportsShouldProcess=$true 45 | )] 46 | 47 | param( 48 | [String] 49 | [Parameter( 50 | Position=1, 51 | Mandatory=$true 52 | )] 53 | [ValidateScript({ ($_ -ne '') -and ( Test-Path $_ ) })] 54 | $InstallerPath, 55 | 56 | [String] 57 | [Parameter( 58 | Position=2 59 | )] 60 | [ValidateNotNullOrEmpty()] 61 | $KBID, 62 | 63 | [String] 64 | [Parameter( 65 | Position=3 66 | )] 67 | [ValidateScript({ ( $_ -ne '' ) -and ( Test-Path $_ ) })] 68 | $LogPath, 69 | 70 | [Boolean] 71 | [Parameter( 72 | Position=4 73 | )] 74 | $Force 75 | ) # Param 76 | 77 | Function Add-LogEntry ( [String]$Path ,[String]$Message) 78 | { 79 | Write-Verbose -Message $Message 80 | # Only write log entry if a path was specified 81 | If ( $Path -ne '' ) { 82 | Add-Content -Path $Path -Value "$(Get-Date): $Message" 83 | } # ( $Path -ne '' ) 84 | } # Function Add-LogEntry 85 | 86 | # If a Log Path was specified get up a log file name to write to. 87 | If (($LogPath -eq '') -or ($LogPath -eq $null)) { 88 | [String]$LogFile = '' 89 | } else { 90 | [String]$LogFile = Join-Path -Path $LogPath -ChildPath "$($ENV:computername)_$KBID.txt" 91 | } # ($LogPath -eq '') 92 | 93 | # Has this update already been installed? 94 | [Boolean]$Installed = $False 95 | If ( (get-wmiobject -Class win32_QuickFixEngineering -Filter "HotfixID = '$KBID'" | Measure-Object).Count -gt 0 ) { 96 | [Boolean]$Installed = $True 97 | } # ( (get-wmiobject -Class win32_QuickFixEngineering -Filter "HotfixID = '$KBID'" | Measure-Object).Count -gt 0 ) 98 | 99 | # This application or update is not installed - so install it. 100 | If (-not $Installed) { 101 | If ([io.path]::GetExtension($InstallerPath) -eq '.msu') { 102 | [String]$Command="WUSA.EXE $InstallerPath /quiet /norestart)" 103 | [String]$Type="MSU $KBID" 104 | } else { 105 | [String]$Command="$InstallerPath /quiet /norestart" 106 | [String]$Type="EXE $KBID" 107 | } 108 | 109 | Add-LogEntry -Path $LogFile -Message "Install $Type using $Command started." 110 | If ($PSCmdlet.ShouldProcess("Install $Type using $Command started")) { 111 | # Call the product Install. 112 | & cmd.exe /c "$Command" 113 | [Int]$ErrorCode = $LASTEXITCODE 114 | } # ShouldProcess 115 | Switch ($ErrorCode) { 116 | 0 { Add-LogEntry -Path $LogFile -Message "Install $Type using $Command completed successfully." } 117 | 1641 { Add-LogEntry -Path $LogFile -Message "Install $Type using $Command completed successfully and computer is rebooting." } 118 | default { Add-LogEntry -Path $LogFile -Message "Install $Type using $Command failed with error code $ErrorCode." } 119 | } # ($ErrorCode) 120 | } Else { 121 | Write-Verbose -Message "$Type is already installed." 122 | } # (-not $Installed) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Powershell 2 | ========== 3 | 4 | ## Install using GPO Tools 5 | This project contains scripts for installing applications or updates via GPO. 6 | 7 | ### Overview 8 | This contains two PowerShell scripts that will install either an Application or an Windows QFE Update. These scripts are designed to be used with Startup/Logon GPO scripts to install these updates. The registry or WMI will be checked to see if the Application or Update is already installed. If it is installed then the process will be skipped. 9 | 10 | I wrote these scripts initally to automate the process of installing Notepad++ and the WMF 5.0 preview in my lab environemt. It is based loosely on the code I wrote in the OfficeTools scripts (for installing Office via GPO). 11 | 12 | ### Install-Application 13 | Installs an Application from a local or network media source if a registry key/value is not set. 14 | 15 | ####Description 16 | Installs an Application from a specified media source by executing the setup installer (.EXE) file. 17 | 18 | A registry key must also be provided to check for to identify if the application is already installed. Optionally a registry value in the registry key can also be checked for. 19 | 20 | This script would normally be used with the Windows Server 2012 GPO PowerShell Start up Script feature to install a specific application. 21 | 22 | ####Examples 23 | Install Notepad++ 6.7.8.2 without creating a logfile: 24 | ```powershell 25 | Install-Application -InstallerPath '\\server\Software$\Notepad++\npp.6.7.8.2.Installer.exe' -RegistryKey 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Notepad++' -RegistryName 'DisplayVersion' -RegistryValue '6.7.8.2' -InstallerParameters '/S' 26 | ``` 27 | 28 | Install Notepad++ 6.7.8.2 creating log files for each machine it is installed on in \\Server\Software$\logfiles\ folder: 29 | ```powershell 30 | Install-Application -InstallerPath '\\server\Software$\Notepad++\npp.6.7.8.2.Installer.exe' -RegistryKey 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Notepad++' -RegistryName 'DisplayVersion' -RegistryValue '6.7.8.2' -InstallerParameters '/S' -LogPath \\Server\Software$\logfiles\ 31 | ``` 32 | 33 | See: 34 | ```powershell 35 | Get-Help .\Install-Application.ps1 -Full 36 | ``` 37 | For more information. 38 | 39 | 40 | ### Install-Update 41 | Installs a Windows QFE Update from a local or network media source. 42 | 43 | ####Description 44 | Installs a Windows QFE Update from a specified media source by executing the update installer (.EXE) or Microsoft Update (.MSU) file. 45 | 46 | This script would normally be used with the Windows Server 2012 GPO PowerShell Start up Script feature to install a specific application or update. 47 | 48 | Normally WSUS would be used to distribute and install QFE updates, but some updates are not always available via this method (Windows Management Framework 3.0 and above for example). SCCM could be used instead but this is for sites not using SCCM. 49 | 50 | ####Examples 51 | To install the Windows Management Framework 5.0 April 2015 update with no log file creation: 52 | ```powershell 53 | Install-Update -InstallerPath \\Server\Software$\Updates\WindowsBlue-KB3055381-x64.msu -KBID KB3055381 54 | ``` 55 | 56 | To install the Windows Management Framework 5.0 April 2015 update creating log files for each machine it is installed on in \\Server\Software$\logfiles\ folder: 57 | ```powershell 58 | Install-Update -InstallerPath \\Server\Software$\Updates\WindowsBlue-KB3055381-x64.msu -KBID KB3055381 -LogPath \\Server\Software$\logfiles\ 59 | ``` 60 | 61 | See: 62 | ```powershell 63 | Get-Help .\Install-Update.ps1 -Full 64 | ``` 65 | For more information. 66 | 67 | ### Minimum requirements 68 | 69 | - PowerShell 4.0 70 | 71 | 72 | ### License and Copyright 73 | 74 | Copyright 2014 Daniel Scott-Raynsford 75 | 76 | Licensed under the Apache License, Version 2.0 (the "License"); 77 | you may not use this file except in compliance with the License. 78 | You may obtain a copy of the License at 79 | 80 | http://www.apache.org/licenses/LICENSE-2.0 81 | 82 | Unless required by applicable law or agreed to in writing, software 83 | distributed under the License is distributed on an "AS IS" BASIS, 84 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 85 | See the License for the specific language governing permissions and 86 | limitations under the License. 87 | --------------------------------------------------------------------------------