Using Powershell W/ Web Platform Installer API Only Fetches x86 Installers on 64 bit Machine - windows

I'm trying to write a script to automate the installation of the Application Request Routing package on a x64 Windows Server 2012 R2 with IIS 8 and Web Platform Installer 5. I've reproduced the code I'm using below:
Try {
[reflection.assembly]::LoadWithPartialName("Microsoft.Web.PlatformInstaller") | Out-Null
$ProductManager = New-Object Microsoft.Web.PlatformInstaller.ProductManager
$ProductManager.Load()
$product = $ProductManager.Products | Where { $_.ProductId -eq "ARRv3_0" }
#Get an instance of InstallManager class to perform package install
$InstallManager = New-Object Microsoft.Web.PlatformInstaller.InstallManager
$installer = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Installer]'
$Language = $ProductManager.GetLanguage("en")
#Get dependencies
$deplist = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Product]'
$deplist.add($product)
$deps = $product.getMissingDependencies($deplist)
foreach ($dep in $deps) {
Write-Host "$($dep.GetInstaller($Language))"
$Installer.Add($dep.GetInstaller($Language))
Write-Host "Dependency $($dep.Title) not found..."
}
$installer.Add($product.Installers[1])
$InstallManager.Load($installer)
#Download the installer package
$failureReason=$null
foreach ($installerContext in $InstallManager.InstallerContexts) {
$InstallManager.DownloadInstallerFile($installerContext, [ref]$failureReason)
Write-Host $($installerContext)
}
$InstallManager.StartSynchronousInstallation()
notepad $product.Installers[1].LogFiles
Write-Host "Opening logs at $($product.Installers[1].LogFiles)"
Write-Host "Installation finished"
}
Catch {
Write-Error "FATAL ERROR! $($_)"
}
Finally {
Write-Host "Press any key to continue ..."
$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
The ARRv3_0 has two dependencies, ExternalCache and UrlRewrite2.
However, when I try to pull the installers using:
$Language = $ProductManager.GetLanguage("en")
$installer.Add($dep.GetInstaller($Language))
(where $dep is the reference to the product) it only fetches the x86 version, which will not install on a 64 bit machine. I've looked through the ProductList Xml that contains a listing of the Web Platform Packages here, and I've copied and pasted below the occurrence of an x64 variant of the UrlRewrite2 package, which exists.
<installer>
<id>20</id>
<languageId>en</languageId>
<architectures>
<x64/>
</architectures>
<eulaURL>
......
</installer>
Interestingly enough, there's an architecture parameter, but looking at the Microsoft.Web.PlatformInstaller API there doesn't seem to be a way to set/access it. Other than hardcoding, is there any possible way to tell the API to fetch the 64 bit versions instead?
I'm definitely running this in a 64 bit powershell on a 64 bit machine, but it seems incredibly counter intuitive that the api would fetch x86 installers. Is there some incredibly obvious (and poorly documented) setting that I'm missing?

The Product class as the Installers property. Instead of getting the default installer, I get a specific installer (64bit) and add it to a generic list of installers that is passed as argument to the InstallManager. Here is the snippet.
$installers = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Installer]'
foreach($i in $product.Installers)
{
if($i.InstallerFile.InstallerUrl.ToString().ToLower().EndsWith("_amd64.msi"))
{
$i.InstallerFile
$installers.Add($i)
break
}
}

Related

Get a file path from the explorer menu to a Powershell variable

I need to make a API call where file upload operation is required how can I prompt user to select the file from explorer and use the path after storing in a variable. I found similar question but it only works for folder.
On Windows, you can take advantage the OpenFileDialog Windows Forms component:
function Select-File {
param([string]$Directory = $PWD)
$dialog = [System.Windows.Forms.OpenFileDialog]::new()
$dialog.InitialDirectory = (Resolve-Path $Directory).Path
$dialog.RestoreDirectory = $true
$result = $dialog.ShowDialog()
if($result -eq [System.Windows.Forms.DialogResult]::OK){
return $dialog.FileName
}
}
Then use like so:
$path = Select-File
if(Test-Path $path){
Upload-File -Path $path
}
Mathias's answear is great, there's just one issue.
You first need to load the System.Windows.Forms assembly, as this article states!

How to fix security within WinSCP SFTP scripts in PowerShell with hard-coded passwords

So my organization has tasked me with cleaning up some of the security issues in regards to some automated scripts that have hard coded passwords within the scripts that are running as automated tasks. One such task contains SFTP scripts that export and import files to and from with the password, host name, credentials, port, and everything exposed within the script. As a result, I would like to first see about how to call such credentials within a separate file that can be hidden and two see about encryption and salting it later. But my main focus is getting them out of the script in case traffic is every intercepted. Here is what the PowerShell code looks like:
param (
$localPath = 'E:\FTP\SchooLinks\course_requests.csv',
$remotePath = '/schoolinks_exports/course_planning/course_requests.csv'
)
try
{
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "<domain_name>"
UserName = "<username>"
Password = "<password>"
SshHostKeyFingerprint = "<fingerprint>"
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Upload files
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
$transferResult =
$session.GetFiles($remotePath, $localPath, $False, $transferOptions)
# Throw on any error
$transferResult.Check()
# Print results
foreach ($transfer in $transferResult.Transfers)
{
Write-Host "Download of $($transfer.FileName) succeeded"
}
}
finally
{
# Disconnect, clean up
$session.Dispose()
}
exit 0
}
catch
{
Write-Host "Error: $($_.Exception.Message)"
exit 1
}
Another one that we have looks like this:
param (
$localPath = 'E:\FTP\TalentEd\SkywardApplicantExportSQL.txt',
$remotePath = '/SkywardApplicantExportSQL.txt'
)
try
{
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "<domain>"
UserName = "<username>"
Password = "<password>"
SshHostKeyFingerprint = "<sha_fingerprint>"
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Upload files
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
$transferResult =
$session.GetFiles($remotePath, $localPath, $False, $transferOptions)
# Throw on any error
$transferResult.Check()
# Print results
foreach ($transfer in $transferResult.Transfers)
{
Write-Host "Download of $($transfer.FileName) succeeded"
}
}
finally
{
# Disconnect, clean up
$session.Dispose()
}
exit 0
}
catch
{
Write-Host "Error: $($_.Exception.Message)"
exit 1
}
I am familiar with Python and json and calling stuff within a json file similar to the following:
import json
with open('secrets.json','r') as f:
config = json.load(f)
and calling it with (config['object']['nested_element']) within the Python script.
I would like to do something similar with PowerShell, however I have very limited knowledge to PowerShell.
Yeppers, of course, never store creds in clear text in files.
There are several ways to store credentials for use. Secure file (xml, etc..), the registry, or Windows Credential Manager and this is well documented on Microsoft sites, as well as in many articles all over the web and via Q&A's on StackOverflow.
Just search for 'securely store credentials PowerShell'
Sample results...
Working with Passwords, Secure Strings and Credentials in Windows
PowerShell
How to run a PowerShell script against multiple Active Directory
domains with different credentials
Accessing Windows Credentials Manager from PowerShell
Save Encrypted Passwords to Registry for PowerShell
...and/or the modules via the MS powershellgallery.com directly installable from your PowerShell environments.
Find-Module -Name '*cred*' |
Format-Table -AutoSize
<#
# Results
Version Name Repository Description
------- ---- ---------- -----------
2.0 CredentialManager PSGallery Provides access to credentials in the Windows Credential Manager
2.0.168 VcRedist PSGallery A module for lifecycle management of the Microsoft Visual C++ Redistributables. Downloads the supp...
1.3.0.0 xCredSSP PSGallery Module with DSC Resources for WSMan CredSSP.
1.1 VPNCredentialsHelper PSGallery A simple module to set the username and password for a VPN connection through PowerShell. Huge tha...
1.0.11 pscredentialmanager PSGallery This module allows management and automation of Windows cached credentials.
4.5 BetterCredentials PSGallery A (compatible) major upgrade for Get-Credential, including support for storing credentials in Wind...
1.0.4 WindowsCredential PSGallery Management module for Windows Credential Store.
...
#>
So many thanks to #postanote and #Martin Prikryl I was able to figure this out.
You can basically use a config.xml file with contents similar to this:
<Configuration>
<localPath>insert_local_file_path</localPath>
<remotePath>insert_remote_file_path</remotePath>
<Protocol>[WinSCP.Protocol]::Sftp</Protocol>
<HostName>insert_hostname</HostName>
<UserName>username</UserName>
<Password>mypassword</Password>
<SshHostKeyFingerPrint>fingerprint</SshHostKeyFingerPrint>
</Configuration>
From here you can use the following at the beginning of your template:
# Read XML configuration file
[xml]$config = Get-Content ".\config.xml"
param (
$localPath = $config.Configuration.localPath
$remotePath = $config.Configuration.remotePath
)
try
{
# Load WinSCP .NET assembly
Add-Type -Path "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = $config.Configuration.Protocol
HostName = $config.Configuration.HostName
UserName = $config.Configuration.UserName
Password = $config.Configuration.Password
SshHostKeyFingerprint = $config.Configuration.SshHostKeyFingerprint
}
I have more SFTP templates here people can use at
https://github.com/Richard-Barrett/ITDataServicesInfra/tree/master/SFTP

Windows Update Agent API - Searching for Updates

I wrote a Powershell script that uses the Windows Update Agent API (IUpdateSearcher, IUpdateDownloader, IUpdateInstaller etc.). Everything works fine, the script finds avaiable updates, downloads and installs them.
However, there is a problem when searching for consecutive updates. For example, there is an update for the .Net Framework 4.5.2. The update is installed by script and the PC is rebooted afterwards. Now there should be an update for the .Net Framework 4.5.2 Language Pack avaiable.
But it is not. At least not via the API. A manual search with the GUI (Windows Update) works.
After the manual search, the API finds the update a well!
What am I missing in my script? I could not find anything in Microsofts documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/aa386868(v=vs.85).aspx
$updateSession = New-Object -ComObject 'Microsoft.Update.Session'
$UpdateSession.WebProxy.AutoDetect = $false
$updateSearcher = $updateSession.CreateUpdateSearcher()
$searchResult = $updateSearcher.Search('IsInstalled=0 and IsHidden=0')
$objCollectionDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl'
foreach ($update in $searchResult.Updates)
{
$objCollectionTmp = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$objCollectionTmp.Add($update) | Out-Null
$downloader = $updateSession.CreateUpdateDownloader()
$downloader.Updates = $objCollectionTmp
try
{
$downloadResult = $downloader.Download()
}
catch
{
//exception Handling
}
$objCollectionDownload.Add($update) | Out-Null
}
$updatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$updateInstaller = $updateSession.CreateUpdateInstaller()
foreach ($update in $objCollectionDownload)
{
//accept Eula etc...
$updatesToInstall.Add($update) | Out-Null
}
$updateInstaller.Updates = $updatesToInstall
$installationRestult = $updateInstaller.Install()
//check installation result
Oddly enough I had the same issue just now, Windows GUI showed a particular update, Our GUI using the API wouldn't show this particular update... I had IsInstalled = 0 and IsHidden = 0.... I looked in the WIndows Update log and found the criteria that the WIndows GUI was using.
IsInstalled=0 and DeploymentAction='Installation' or IsPresent=1 and DeploymentAction='Uninstallation' or IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1
Added this to my application in place of IsInstalled = 0 and IsHidden = 0 and the update showed straight up :-/ don't really understand why but I am not complaining.

Is there an easy way to check if CredSSP is enabled on a systems?

I am aware of the Get-WSManCredSSP function; however, this cmdlet does not work well in a script. This returns a long string similar to the following:
The machine is configured to allow delegating fresh credentials to the following target(s): wsman/*,wsman/*,wsman/*,wsman/*
This computer is configured to receive credentials from a remote client computer.
I cannot easily include this in a script that I am writing, so I'm looking for an alternative way to check CredSSP.
Can't you consider using this as documented in the CmdLet help: Gets the WS-Management CredSSP setting on the client (<localhost|computername>\Client\Auth\CredSSP).
On a local machine it gives :
(Get-Item WSMan:\localhost\Client\Auth\CredSSP).value
You can use it like this :
(Get-Item WSMan:\localhost\Client\Auth\CredSSP).value -eq $false
You can first test if WinRm is available :
(Get-Service -Name winrm ).Status
I was also struggling with the limitations of the Get-WSManCredSSP output, and found this helper script by Victor Vogelpoel/Ravikanth Chaganti to be really helpful.
Some examples:
Check if current machine has been configured as CredSSP server and/or client:
(Get-WSManCredSSPConfiguration).IsServer
(Get-WSManCredSSPConfiguration).IsClient
Check if a specified client machine has been set up for delegation:
Get-WSManCredSSPConfiguration | % { $_.ClientDelegateComputer.Contains('clientcomputername') }
(not intended as a replacement for the work of Vogelpoel & Chaganti, but as a quick summary of a quick reading of CredSSP.cs, so you can get a quick grasp of what it's doing - that said, it was tested on several systems I had at hand and seems to work)
function Get-WSManCredSSPState
{
$res = [pscustomobject]#{DelegateTo = #(); ReceiveFromRemote = $false}
$wsmTypes = [ordered]#{}
(gcm Get-WSManCredSSP).ImplementingType.Assembly.ExportedTypes `
| %{$wsmTypes[$_.Name] = $_}
$wmc = new-object $wsmTypes.WSManClass.FullName
$wms = $wsmTypes.IWSManEx.GetMethod('CreateSession').Invoke($wmc, #($null,0,$null))
$cli = $wsmTypes.IWSManSession.GetMethod('Get').Invoke($wms, #("winrm/config/client/auth", 0))
$res.ReceiveFromRemote = [bool]([xml]$cli).Auth.CredSSP
$afcPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentials'
if (test-path $afcPath)
{
$afc = gi $afcPath
$res.DelegateTo = $afc.GetValueNames() | sls '^\d+$' | %{$afc.GetValue($_)}
}
return $res
}

Creating a zipped/compressed folder in Windows using Powershell or the command line

I am creating a nightly database schema file and would like to put all the files created each night, one for each database, into a folder and compress that folder.
I have a PowerShell script that creates the schema.Only creation script of the db's and then adds all the files to a new folder. The problem lies within the compression portion of this process.
Does anybody have any idea if this can be accomplished with the pre-installed Windows utility that handles folder compression?
It would be best to use that utility if possible rather than something like 7zip (I don't feel like installing 7zip on every customers' server and it may take IT years to do it if I ask them).
A native way with latest .NET 4.5 framework, but entirely feature-less:
Creation:
Add-Type -Assembly "System.IO.Compression.FileSystem" ;
[System.IO.Compression.ZipFile]::CreateFromDirectory("c:\your\directory\to\compress", "yourfile.zip") ;
Extraction:
Add-Type -Assembly "System.IO.Compression.FileSystem" ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("yourfile.zip", "c:\your\destination") ;
As mentioned, totally feature-less, so don't expect an overwrite flag.
Here's a couple of zip-related functions that don't rely on extensions: Compress Files with Windows PowerShell.
The main function that you'd likely be interested in is:
function Add-Zip
{
param([string]$zipfilename)
if(-not (test-path($zipfilename)))
{
set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(dir $zipfilename).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipfilename)
foreach($file in $input)
{
$zipPackage.CopyHere($file.FullName)
Start-sleep -milliseconds 500
}
}
Usage:
dir c:\demo\files\*.* -Recurse | Add-Zip c:\demo\myzip.zip
There is one caveat: the shell.application object's NameSpace() function fails to open up the zip file for writing if the path isn't absolute. So, if you passed a relative path to Add-Zip, it'll fail with a null error, so the path to the zip file must be absolute.
Or you could just add a $zipfilename = resolve-path $zipfilename at the beginning of the function.
As of PowersShell 5 there is a Compress-Archive cmdlet that does the task out of the box.
This compresses .\in contents to .\out.zip with System.IO.Packaging.ZipPackage following the example here
$zipArchive = $pwd.path + "\out.zip"
[System.Reflection.Assembly]::Load("WindowsBase,Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
$ZipPackage=[System.IO.Packaging.ZipPackage]::Open($zipArchive, [System.IO.FileMode]"OpenOrCreate", [System.IO.FileAccess]"ReadWrite")
$in = gci .\in | select -expand fullName
[array]$files = $in -replace "C:","" -replace "\\","/"
ForEach ($file In $files) {
$partName=New-Object System.Uri($file, [System.UriKind]"Relative")
$part=$ZipPackage.CreatePart($partName, "application/zip", [System.IO.Packaging.CompressionOption]"Maximum")
$bytes=[System.IO.File]::ReadAllBytes($file)
$stream=$part.GetStream()
$stream.Write($bytes, 0, $bytes.Length)
$stream.Close()
}
$ZipPackage.Close()
Used voithos' answer to zip files up in powershell, just had one problem with the Add-Zip function, the Start-sleep -milliseconds 500 caused problems if the file couldn't be fully zipped up in that time -> the next one starting before it was complete caused errors and some files not to be zipped.
So after playing around for a bit, first trying to get a counter going to check the count of the $zipPackage.Items() and only continuing after the items count increased (which did not work as it would return 0 in some cases when it should not) I found that it will return 0 if the package is still zipping/copying the files up (I think, haha). Added a simple while loop with the start-sleep inside of it, waiting for the zipPackage.Items().count to be a non-zero value before continuing and this seems to solve the problem.
function Add-Zip
{
param([string]$zipfilename)
if(-not (test-path($zipfilename)))
{
set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(dir $zipfilename).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipfilename)
foreach($file in $input)
{
$zipPackage.CopyHere($file.FullName)
do
{
Start-sleep -milliseconds 250
}
while ($zipPackage.Items().count -eq 0)
}
}
Using PowerShell Version 3.0:
Copy-ToZip -File ".\blah" -ZipFile ".\blah.zip" -Force
Hope this helps.

Resources