Azure Pipelines: How to do successful BITS-Transfer in Powershell task? - powershell

Can't find the answer for this anywhere.
I need my pipeline to call a powershell script that has to do a BITS-Transfer multiple times. At first the script would always exit with this error:
Start-BitsTransfer : The operation being requested was not performed because the user has not
logged on to the
network. The specified service does not exist. (Exception from HRESULT: 0x800704DD)
Then, in the Powershell task, instead of calling the file, I changed it to do this inline script:
powershell -File $(Build.Repository.LocalPath)\installer\assembly.win10.x64.ps1 -Credential Get-Credential [Domain]\[Username]
Now the script runs, but I get this in my log in Azure Pipelines:
Microsoft Visual C++ 2015 Redistributable x64 : downloading...
Start-BitsTransfer : The operation being requested was not performed because the user has not
logged on to the
network. The specified service does not exist. (Exception from HRESULT: 0x800704DD)
At C:\agents\_work\3\s\installer\assembly.win10.x64.ps1:9 char:5
+ Start-BitsTransfer -Source $Url -Destination $Target
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Start-BitsTransfer], COMException
+ FullyQualifiedErrorId :
System.Runtime.InteropServices.COMException,Microsoft.BackgroundIntelligentTransfer.Management.NewBitsTransferCommand
Microsoft Visual C++ 2015 Redistributable x64 : download completed
The pipeline passes, but when I check the actual redistributable's .exe, it has a size of 0 bytes. So, it seems the folders are made and zipped, and the files are where they need to be, but the download didn't actually work. I also tested the powershell script on my machine locally, and there are no problems. How can I fix this?
Here is the relevant code. First three blocks are the download-related methods, then lastly is the method call:
function Get-FileSha256([string]$Src) {
return (Get-FileHash "$Src" -Algorithm SHA256).Hash
}
function Download-File([string]$Comment, [string]$Url, [string]$Target) {
Write-Host "$Comment : downloading..."
New-Item -ItemType File -Path "$Target" -Force | Out-Null
Import-Module BitsTransfer
Start-BitsTransfer -Source $Url -Destination $Target
Write-Host "$Comment : download completed"
}
function Download-FileIfNecessary([string]$Comment, [string]$Url,
[string]$Target, [string]$Sha256) {
If (!(Test-Path $Target)) {
Download-File "$Comment" "$Url" "$Target"
return
}
$TargetSha256 = Get-FileSha256 $Target
If ($TargetSha256 -eq $Sha256) {
Write-Host "$Comment : already downloaded"
}
Else {
Download-File "$Comment" "$Url" "$Target"
}
}
Download-FileIfNecessary "Microsoft Visual C++ 2015 Redistributable x64" `
"https://download.microsoft.com/download/6/A/A/6AA4EDFF-645B-48C5-81CC-ED5963AEAD48/vc_redist.x64.exe" `
"$target_downloaded_vc_redist_dir\2015.v14.0.24215\vc_redist.x64.exe" `
"da66717784c192f1004e856bbcf7b3e13b7bf3ea45932c48e4c9b9a50ca80965"

I'm going to offer an alternative to using BITS, as BITS is designed to be used when the requestor is a local authenticated user, not by remote sessions, service accounts, or other non-interactive contexts. I've re-written Download-File to make use of Invoke-WebRequest instead ofStart-BitsTransfer:
Function Download-File {
Param(
[string]$Comment,
[string]$Url,
[string]$Target
)
Write-Host "$Comment : downloading..."
# We need to check that the target folder exists later
$targetFolder = Split-Path -Parent $Target
# Prepare cmdlet arguments for splatting
$iwrArgs = #{
UseBasicParsing = $true
Uri = $Url
OutFile = $Target
}
$newDirArgs = #{
ItemType = 'Directory'
Path = $targetFolder
}
# Create the target parent dir, if it doesn't exist
if ( !( Test-Path -PathType Container $targetFolder ) ) {
Write-Host "Creating directory $targetFolder"
New-Item #newDirArgs | Out-Null
}
# Workaround progress bar performance bug with Invoke-WebRequest
# (also affects Invoke-RestMethod) by disabling it
$OldProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
# Use a try / finally block to guarantee the
# original progress preference is changed back
try {
Invoke-WebRequest #iwrArgs -EA Stop
}
finally {
$ProgressPreference = $OldProgressPreference
}
}
I've commented the code if you're interested in what each piece is doing. I can clarify if there are any questions.
You should be able to drop this in place of your current Download-File definition. I would recommend this over trying hacky OS tricks to make BITS work in a context it's not designed to. BITS is also not recommended for use with critical automation, as it downloads can be suspended by the OS at any time (e.g. when updates are being downloaded).
Note that what I've said is true for non-interactive logins. BITS should be safe to use from a scheduled task or user-run script, just not from a Windows service (which the Azure Agent is).

Related

Chng Execution Policy in RemotePSSession

I am trying to write something that will prompt for a PC name and then remotely perform a folder permission change. Everything is chaotic looking but I am struggling with changing the execution policy after the Enter-PSSession - it seems it just gets stuck and won't proceed further to my function.
This is the rough draft of what I have thus far. Any help is appreciated.
$PromptedPC = Read-Host -Prompt "`n `n Enter PC Number"
Enter-PSSession -ComputerName $PromptedPC
Write-Host `n"Do you want to change folder permissions?" -ForegroundColor Green
$ReadAnswer = Read-Host " ( y / n ) "
PowerShell.exe -noprofile -ExecutionPolicy Bypass
switch ($ReadAnswer)
{
Y {
function Grant-userFullRights {
[cmdletbinding()]
param(
[Parameter(Mandatory=$true)]
[string[]]$Files,
[Parameter(Mandatory=$true)]
[string]$UserName
)
$rule=new-object System.Security.AccessControl.FileSystemAccessRule ($UserName,"FullControl","Allow")
foreach($File in $Files) {
if(Test-Path $File) {
try {
$acl = Get-ACL -Path $File -ErrorAction stop
$acl.SetAccessRule($rule)
Set-ACL -Path $File -ACLObject $acl -ErrorAction stop
Write-Host "Successfully set permissions on $File"
} catch {
Write-Warning "$File : Failed to set perms. Details : $_"
Continue
}
} else {
Write-Warning "$File : No such file found"
Continue
}
}
}
}
}
Grant-userFullRights -Files 'C:\ProgramData\New World Systems\' -UserName "BUILTIN\Users"
I did get the function information from: http://techibee.com/powershell/grant-fullcontrol-permission-to-usergroup-on-filefolder-using-powershell/2158
I am just trying to put something together that will work for a programdata folder our users need full control of that keeps reverting permissions to something lower than full access.
You start a new PowerShell with the ExecutionPolicy Bypass.
Sine you are already in PowerShell, the proper way to configure the Execution Polcy is Set-ExecutionPolicy.
However, since you don't invoke any script file, there is no need to change the Execution Policy.

powershell input string not in correct format

I am having a bit of an annoying problem that I just can't seem to find the answer to. I am using a powershell script to uninstall programs and a batch file to run the script on the programs I want removed in a particular order. I found the script on the web and it works perfectly on my test machine. The problem begins when I run the script on another machine.
This is the error I get:
Error formatting a string: Input string was not in a correct format..
At C:\Users\currentuser\Desktop\uninstallScript.ps1:60 char:1
+ &msiexec `/qn `/x `{$stringer`}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: ({{0}}}:String) [],
RuntimeException
+ FullyQualifiedErrorId : FormatError
And here is the PowerShell script:
###########################################
######## Written by DC 2012-02-13#########
###########################################
<#
.SYNOPSIS
Uninstalls software by only passing the Software Title.
Should work with all msiexec string uninstallers.
For uninstall commands that end in uninstall.exe or helper.exe a "/S" is
used as a switch.
.PARAMETER DisplayName
The complete or partial name of the software being uninstalled. Must appear
as shown in add / remove programs (case insenstive).
.EXAMPLE
Uninstall-Program Java
Will search the registry and uninstall all instances of Java from a machine.
#>
[cmdletBinding()]
Param
(
[String]$DisplayName = $(throw "DisplayName is Required")
)
Set-Variable -Name ThirtyMachine -Value
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Option Constant
Set-Variable -Name SixtyMachine -Value
"HKLM:\SOFTWARE\WOW6432NODE\Microsoft\Windows\CurrentVersion\Uninstall" -
Option Constant
Set-Variable -Name ThirtyUser -Value
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Option Constant
Set-Variable -Name SixtyUser -Value
"HKCU:\SOFTWARE\WOW6432NODE\Microsoft\Windows\CurrentVersion\Uninstall" -
Option Constant
$regs = $ThirtyMachine,$SixtyMachine,$ThirtyUser,$SixtyUser
foreach ($reg in $regs)
{
if(Test-Path $reg)
{
$SubKeys = Get-ItemProperty "$reg\*"
}
else
{
$SubKeys = $null
}
foreach($key in $SubKeys)
{
if($key.DisplayName -match "$DisplayName")
{
Write-Host "Found Software " $key.DisplayName
if($key.UninstallString -match "^msiexec")
{
$startGUID = $key.UninstallString.IndexOf("{") + 1
$endGuid = $key.UninstallString.IndexOf("}") - $startGUID
$stringer = $key.UninstallString.Substring($startGUID,$endGuid)
Write-Host "Uninstaller Known, now uninstalling"
&msiexec `/qn `/x `{$stringer`}
}
if($key.UninstallString.Replace('"',"") -match 'uninstall.exe\Z' -or
$key.UninstallString.replace('"',"") -match 'helper.exe\Z' )
{
$stringer = $key.UninstallString.Replace('"',"")
if(Test-Path $stringer )
{
Write-Host "Possible Uninstaller found. Trying" $key.UninstallString "/S"
&$stringer /S
}
}
}
}
}
I debugged the code in PowerShell ISE to verify that the variable $stringer contained the correct value. So I believe this has something to do with the version of powershell being run on the 2 machines. The test machine, where the script works, is version 2 whereas the other machine, where I get the error, is version 4. I've barely begun to learn powershell so I am scratching my head at this one. Hopefully it is simple to resolve. I appreciate any help offered.
The escape characters ``` in your msiexec call are causing the error and shouldn't be there. Here's a way to call it how you are properly:
& msiexec /qn "/x{$stringer}"
Alternatively, you can utilize Start-Process and wait on the process:
Start-Process -FilePath 'msiexec' -ArgumentList #('/qn','/x',"{$stringer}") -Wait

Executing XMLA with AMO PowerShell does not create measures

I wrote a PowerShell script to automate deployment of SSAS Cubes. I use the Deployment Wizard to generate an XMLA file and then PowerShell AMO command to deploy it. However, when I run it the tabular database gets created but all measures are missing. Running the same XMLA from SQL Management Studio or using Invoke-ASCmd produce the correct database with all measures in it. Am I missing an option or something in the "Invoke" command?
[CmdletBinding()]
Param(
# InputDir is required
[Parameter(Mandatory=$True,Position=1)]
[string]$InputDir,
# Server is optional
[Parameter(Mandatory=$False,Position=2)]
[string]$Server=$env:computername
)
# Output execution parameters.
"Executing with the following parameters:"
" InputDir: $InputDir"
" AS Database Server: $Server`n"
$XmlaDir = Resolve-Path($InputDir)
$Xmla = Join-Path -Path $XmlaDir -ChildPath '\Model.xmla'
$ASFiles = Get-ChildItem -Recurse -Path $InputDir -Filter *.asdatabase
$Count = $ASFiles.Count
If($Count -ne 1)
{
Write-Host("`ERROR: Count asdatabase file(s) found at $inputdir")
Exit 1
}
$ASDatabase = $ASFiles[0].FullName
Write-Host("`nUsing $ASDatabase for deployment.")
Write-Host("`nAttempting to create $Xmla ...`n")
# Use Analysis Services Deployment Utility to generate XMLA file from
.asdatabase file
$Script:ASDeployWizard = "E:\Program Files (x86)\Microsoft SQL Server\110\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe
"
$Arguments = #("`"$ASDatabase`"", "/s", "/o:`"$Xmla`"")
Start-Process -FilePath $Script:ASDeployWizard -ArgumentList $Arguments -Wait
If ((-Not $?) -Or -Not (Test-Path $Xmla))
{
"Cannot generate deployment descriptor. Deployment aborted."
Exit 1
}
Write-Host("XMLA deployment descriptor generated.`n")
Try {
Import-Module SQLPS -DisableNameChecking
# Deploy Cube
"Invoking deployment script. This may take several minutes...`n"
$AS = New-Object Microsoft.AnalysisServices.Server
$AS.connect($Server)
$CubeDescriptor = [string](Get-Content $Xmla)
$Results = $AS.Execute($CubeDescriptor)
Foreach ($r in $Results) {
$r.Messages.Description
}
"Done.`n"
} Catch {
Write-Host($_.Exception.GetType().FullName + "`n" + $_.Exception.Message + "`n")
Write-Host("Deployment FAILED.`n")
}
Exit 0

test-path returns permission denied - how to do error handling

i try to do error handling within my powershell script. but i always get an fatal. i tried a few things, e. g. try{ } catch{ } - but i did it not get to work.
any ideas or Solutions?
Function Check-Path($Db)
{
If ((Test-Path $Db) –eq $false) {
Write-Output "The file $Db does not exist"
break
}
}
It Returns:
Test-Path : Zugriff verweigert
In K:\access\access.ps1:15 Zeichen:6
+ If ((Test-Path $Db) -eq $false) {
+ ~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (K:\ss.mdb:String) [Test-Path], UnauthorizedAccessException
+ FullyQualifiedErrorId : ItemExistsUnauthorizedAccessError,Microsoft.PowerShell.Commands.TestPathCommand
Somewhat confusingly Test-Path actually generates an error in a number of cases. Set the standard ErrorAction parameter to SilentlyContinue to ignore it.
if ((Test-Path $Db -ErrorAction SilentlyContinue) -eq $false) {
I cannot answer directly. So this has to do:
I strongly disagree with your answer. Test-Path does show $false when you run it against a network share that is not accessible, but it will be false also (without Exception) when the Server is not reachable.
So your answer simply ignores anything but a reachable share.
What is neccessary however is a try-catch-block that handles this better:
[cmdletbinding()]
param(
[boolean]$returnException = $true,
[boolean]$returnFalse = $false
)
## Try-Catch Block:
try {
if ($returnException) {
## Server Exists, but Permission is denied.
Test-Path -Path "\\Exists\Data\" -ErrorAction Stop | Out-Null
} elseif ($returnFalse) {
## Server does not exist
Test-Path -Path "\\NoExists\Data\" -ErrorAction Stop | Out-Null
}
} catch [UnauthorizedAccessException] {
## Unauthorized
write-host "No Access Exception"
} catch {
## an error has occurred
write-host "Any other Exception here"
}
The really important part however is the ErrorAction on the Test-Path command, otherwise the exception will be wrapped around a system management error and is thus not catchable. This is in detail explained here:
PowerShell catching typed exceptions

Powershell: Update-TfsWorkspace cmdlet how to update two workspaces

I want to update 2 workspaces from two different tfs in one script using powershell.
The first Workspace is updating without any Problems. After the update is finished powershell connects to the second Workspace, but isn't updating the local data like the first time.
I guess the old Connection might still block the pipe or something like that, but I haven't found any cmd to clean the pipe. My code looks like this:
param(
[string]$TestTFS = "http://TestTFS",
[string]$ProdTFS = "http://ProdTFS",
[string]$Teamproject="$\TeamprojectPath",
[string]$LocalTestWorkspace="C:\LocalTestWorkspacePath",
[string]$LocalProdWorkspace="C:\LocalProdWorkspacePath"
)
# Import Microsoft.TeamFoundation.PowerShell Snapin
Add-PSSnapin Microsoft.TeamFoundation.PowerShell
# Connect to production-TFS
$ProdEnvServer = Get-TfsServer -Name $ProdTFS
Write-Host "tfsConnect ="$ProdEnvServer
# Get prod teamprojekt
Get-TfsChildItem $Teamprojekt -Server $ProdEnvServer
# Update files in local prod workspace
Update-TfsWorkspace -Force -Recurse $LocalProdWorkspace
# Connect to test-TFS
$TestEnvServer = Get-TfsServer -Name $TestTFS
Write-Host "tfsConnect ="$TestEnvServer
# Get test teamprojekt
Get-TfsChildItem $Teamprojekt -Server $TestEnvServer
# Update files in local test workspace
Update-TfsWorkspace -Force -Recurse $LocalTestWorkspace
After 3 months and noone coming up with an answer. I just assume that the Cmdlets don't work as they should. The only option here seems to be a Workaround.
# Copy Team Project from Prod to Test TFS
param([string]$TestTFS = "http://TestTFS",
[string]$ProdTFS = "http://ProdTFS",
[String]$Teamproject="$/Teamproject",
[String]$LocalTestWorkspace="C:\LocalTestWorkspacePath",
[String]$LocalProdWorkspace="C:\LocalProdWorkspacePath")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.VersionControl.Client")
try
{
clear
$LocalTestProjectPath = $LocalTestWorkspace + $Teamproject.Substring(1)
$LocalProdProjectPath = $LocalProdWorkspace + $Teamproject.Substring(1)
# Connect to production-TFS
Write-Host "Getting latest of $ProdTFS"
$tfsColProd = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($ProdTFS)
[Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer] $vcsProd = $tfsColProd.GetService([type] "Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer")
# TryGetWorkspace is sometimes buggy and doesn't return an existing workspace
# Delete existing workspace manually before if that happens
$workspaceProd = $vcsProd.TryGetWorkspace($LocalProdWorkspace)
$isProdTempWorkspace = $false
# create Workspace if it doesn't exists
if (-not $workspaceProd) {
Write-Host "No workspace found, creating temporary for prod"
$workspaceProd = $vcsProd.CreateWorkspace("Temp_" + [System.Guid]::NewGuid().ToString())
$workspaceProd.Map($Teamproject, $LocalProdProjectPath)
$isProdTempWorkspace = $true
}
$itemSpecFullTeamProj = New-Object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($Teamproject, "Full")
$fileRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($itemSpecFullTeamProj,
[Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::Latest)
$workspaceProd.Get($fileRequest, [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::GetAll)
if ($isProdTempWorkspace) {
Write-Host "Deleting temporary workspace for prod"
$workspaceProd.Delete()
}
Write-Host "Getting latest of $TestTFS"
$tfsColTest = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($TestTFS)
$vcsTest = $tfsColTest.GetService([type] "Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer")
# TryGetWorkspace is sometimes buggy and doesn't return an existing workspace
# Delete existing workspace manually before if that happens
[Microsoft.TeamFoundation.VersionControl.Client.Workspace] $workspaceTest = $vcsTest.TryGetWorkspace($LocalTestWorkspace)
$isTestTempWorkspace = $false
# create Workspace if it doesn't exists
if (-not $workspaceTest) {
Write-Host "No workspace found, creating temporary for test"
$workspaceTest = $vcsTest.CreateWorkspace("Temp_" + [System.Guid]::NewGuid().ToString())
$workspaceTest.Map($Teamproject, $LocalTestProjectPath)
$isTestTempWorkspace = $true
}
$workspaceTest.Get($fileRequest, [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::GetAll)
# Remove local test folder and copy prod folder into test workspace
Write-Host "Copying over Prod to Test"
# Delete updated test project folder
Remove-Item -Path $LocalTestProjectPath -Force -Recurse
# Copy prod folder to test workspace
Copy-Item -Path $LocalProdProjectPath -Destination $LocalTestProjectPath -Force -Recurse
# Calling tfpt is the only thing that works
Write-Host "Comparing for changes"
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = $env:TFSPowerToolDir + "tfpt.exe"
$ps.StartInfo.Arguments = "online /adds /deletes /diff /noprompt /recursive $LocalTestProjectPath"
$ps.StartInfo.RedirectStandardOutput = $false # careful, only output works, has hanging problems (2k Buffer limit)
$ps.StartInfo.RedirectStandardError = $false
$ps.StartInfo.UseShellExecute = $false
$ps.Start()
$ps.WaitForExit()
# Check in new test project folder into test environment
$wsCheckinParams = New-Object Microsoft.TeamFoundation.VersionControl.Client.WorkspaceCheckInParameters(
#($itemSpecFullTeamProj),"Update project to production environment version")
# CheckIn better manually to check for errors
$workspaceTest.CheckIn($wsCheckinParams)
if ($isTestTempWorkspace) {
Write-Host "Deleting temporary workspace for test"
$workspaceTest.Delete()
Remove-Item -Path D:\Development -Force -Recurse
}
}
catch [System.Exception]
{
Write-Host "Exception: " ($Error[0]).Exception
EXIT $LASTEXITCODE
}
My approach is very similar to Zittelrittel. Just send the path and it will automatically figure out the workspace.
This will not work in PowerShell ISE (x86), I had to use the 64-bit version!
Add-PSSnapin Microsoft.TeamFoundation.PowerShell
Write-Host "Updating Workspace1, please wait..."
Update-TfsWorkspace -item C:\dev\Workspace1\code -Recurse | Format-Table
Write-Host "Updating Workspace2, please wait..."
Update-TfsWorkspace -item C:\dev\Workspace1\code -Recurse | Format-Table
In your calls to update TFS workspace, pipe the result to out-null. This should effectively remove any data that would otherwise be stored in the pipeline.
Update-TfsWorkspace -Force -Recurse $LocalProdWorkspace | Out-Null
Update-TfsWorkspace -Force -Recurse $LocalTestWorkspace | Out-Null