I am using Start-ThreadJob and ScriptBlock to execute a powershell script in a new thread. It works fine on my local but on the preprod server, I am getting an error.
Code Block where I am initiating a new thread
Start-ThreadJob -InputObject $fileType -ScriptBlock {
./Functions/Download-FilesFromFTP.ps1 $args[0] $args[1] $args[2] $args[3] $args[4] $args[5]
} -ArgumentList $ftpServer,$user,$password,$completeSourceFolder,$completeStagingFolderPath,$completeLogFolderPath
As mentioned earlier, this code block works perfectly on my local. On Preprod env I get the following error when I display jobs using Get-Jobs command.
Powershell version on my local
Powershell version on preprod server
The version of the module ThreadJob is same on both servers
Start-ThreadJob runs the new thread with the same current location as the caller, which is unrelated to where the executing script is located.
If you want to refer to a file relative to the script's own location, use the automatic $PSScriptRoot variable, and refer to it in the thread script block via the $using: scope:
Start-ThreadJob -InputObject $fileType -ScriptBlock {
& "$using:PSScriptRoot/Functions/Download-FilesFromFTP.ps1" #args
} -ArgumentList $ftpServer,$user,$password,$completeSourceFolder,$completeStagingFolderPath,$completeLogFolderPath
Note the use of #args in order to also pass all positional arguments, reflected in the automatic $args array, through as individual arguments to the target script via splatting.
On Windows Server 2012R2, how would I go about making a specific python-script (D:/App/applic.py) executable from the terminal at any directory without evoking neither python nor the .py file-ending? On this server, I would like all users to be able to type:
applic 2017 -v
anywhere in the terminal to evoke
python D:/Applic_dev/applic.py 2017 -v
I'm not looking for a py2exe-kind of solution since Python 3 will always be on the server.
PS. My python-script uses plac to parse commandline arguments.
Create a cmd file in a protected location (where users cannot edit it)
applic.cmd
#ECHO OFF
REM This will be wherever the python executable is
SET "PY=%PROGRAMFILES%\Pythonx.x\python.exe"
REM This checks to see if the script is called without arguments
IF [%*] EQU [] (
#CALL %PY% "D:/App/applic.py"
EXIT /B
)
REM %* contains all arguments the script received
#CALL %PY% "D:/App/applic.py" %*
EXIT /B
From here, edit your PATH environment variable to include the path where this protected location is.
Automated PowerShell way
$ProtectedFolderLocation = 'C:\Windows\System32\Applic'
$RegKey = 'HKLM:\System\CurrentControlSet\Control\Session Manager\Environment'
$Path = (Get-ItemProperty -Path $RegKey).Path
If ($Path[-1] -ne ';') { $Path += ";$ProtectedFolderLocation;" }
Else { $Path += "$ProtectedFolderLocation;" }
Set-ItemProperty -Path $RegKey -Name 'PATH' -Value $Path
Now when users call applic from cmd.exe, they'll be calling the script specified above.
We have to build two solutions central on a TFS Server. One solution is a framework, the other includes services, which should be build separately per project in order to deploy them later via script.
In addition the framework assemblies are copied to a (base) project within the framework solution. All projects of the second solution referring to this 'base' project.
My problem is, that I have no idea, how to configure the solutions, project and builds, to behave the request illustrated above.
Please help.
Note: I don't want to put each service project into an msi in order to install it. I just want to deploy the Service out of a central drop-folder on the TFS server.
Team Build can build multiple solutions in the Build Process Template. Just click the [...] button behind the Projects to Build and add both solutions.
TFS redirects the output directory of your projects, which will probably break your script that copies the output from A to the "base project" of B. In order to turn of this redirection set the Output location to AsConfigured.
Now TFS won't know how to copy your output to the Binaries folder, which serves as the source for the copy to droplocation action. To solve that you'll need to write a powershell script and configure this as a post-build script.
The process to create a drop script is clearly documented on MSDN and a sample script is available from CodePlex.
##-----------------------------------------------------------------------
## <copyright file="GatherItemsForDrop.ps1">(c) http://TfsBuildExtensions.codeplex.com/. This source is subject to the Microsoft Permissive License. See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx. All other rights reserved.</copyright>
##-----------------------------------------------------------------------
# Copy the binaries to the bin directory
# so that the build server can drop them
# to the staging location specified on the Build Defaults tab
#
# See
# http://msdn.microsoft.com/en-us/library/bb778394(v=vs.120).aspx
# http://msdn.microsoft.com/en-us/library/dd647547(v=vs.120).aspx#scripts
# Enable -Verbose option
[CmdletBinding()]
# Disable parameter
# Convenience option so you can debug this script or disable it in
# your build definition without having to remove it from
# the 'Post-build script path' build process parameter.
param([switch]$Disable)
if ($PSBoundParameters.ContainsKey('Disable'))
{
Write-Verbose "Script disabled; no actions will be taken on the files."
}
# This script copies the basic file types for managed code projects.
# You can change this list to meet your needs.
$FileTypes = $("*.exe","*.dll","*.exe.config","*.pdb")
# Specify the sub-folders to include
$SourceSubFolders = $("*bin*","*obj*")
# If this script is not running on a build server, remind user to
# set environment variables so that this script can be debugged
if(-not $Env:TF_BUILD -and -not ($Env:TF_BUILD_SOURCESDIRECTORY -and $Env:TF_BUILD_BINARIESDIRECTORY))
{
Write-Error "You must set the following environment variables"
Write-Error "to test this script interactively."
Write-Host '$Env:TF_BUILD_SOURCESDIRECTORY - For example, enter something like:'
Write-Host '$Env:TF_BUILD_SOURCESDIRECTORY = "C:\code\FabrikamTFVC\HelloWorld"'
Write-Host '$Env:TF_BUILD_BINARIESDIRECTORY - For example, enter something like:'
Write-Host '$Env:TF_BUILD_BINARIESDIRECTORY = "C:\code\bin"'
exit 1
}
# Make sure path to source code directory is available
if (-not $Env:TF_BUILD_SOURCESDIRECTORY)
{
Write-Error ("TF_BUILD_SOURCESDIRECTORY environment variable is missing.")
exit 1
}
elseif (-not (Test-Path $Env:TF_BUILD_SOURCESDIRECTORY))
{
Write-Error "TF_BUILD_SOURCESDIRECTORY does not exist: $Env:TF_BUILD_SOURCESDIRECTORY"
exit 1
}
Write-Verbose "TF_BUILD_SOURCESDIRECTORY: $Env:TF_BUILD_SOURCESDIRECTORY"
# Make sure path to binary output directory is available
if (-not $Env:TF_BUILD_BINARIESDIRECTORY)
{
Write-Error ("TF_BUILD_BINARIESDIRECTORY environment variable is missing.")
exit 1
}
if ([IO.File]::Exists($Env:TF_BUILD_BINARIESDIRECTORY))
{
Write-Error "Cannot create output directory."
Write-Error "File with name $Env:TF_BUILD_BINARIESDIRECTORY already exists."
exit 1
}
Write-Verbose "TF_BUILD_BINARIESDIRECTORY: $Env:TF_BUILD_BINARIESDIRECTORY"
# Tell user what script is about to do
Write-Verbose "Will look for and then gather "
Write-Verbose "$FileTypes files from"
Write-Verbose "$Env:TF_BUILD_SOURCESDIRECTORY and copy them to "
Write-Verbose $Env:TF_BUILD_BINARIESDIRECTORY
# Find the files
$files = gci $Env:TF_BUILD_SOURCESDIRECTORY -recurse -include $SourceSubFolders |
?{ $_.PSIsContainer } |
foreach { gci -Path $_.FullName -Recurse -include $FileTypes }
if($files)
{
Write-Verbose "Found $($files.count) files:"
foreach ($file in $files) {
Write-Verbose $file.FullName
}
}
else
{
Write-Warning "Found no files."
}
# If binary output directory exists, make sure it is empty
# If it does not exist, create one
# (this happens when 'Clean workspace' build process parameter is set to True)
if ([IO.Directory]::Exists($Env:TF_BUILD_BINARIESDIRECTORY))
{
$DeletePath = $Env:TF_BUILD_BINARIESDIRECTORY + "\*"
Write-Verbose "$Env:TF_BUILD_BINARIESDIRECTORY exists."
if(-not $Disable)
{
Write-Verbose "Ready to delete $DeletePath"
Remove-Item $DeletePath -recurse
Write-Verbose "Files deleted."
}
}
else
{
Write-Verbose "$Env:TF_BUILD_BINARIESDIRECTORY does not exist."
if(-not $Disable)
{
Write-Verbose "Ready to create it."
[IO.Directory]::CreateDirectory($Env:TF_BUILD_BINARIESDIRECTORY) | Out-Null
Write-Verbose "Directory created."
}
}
# Copy the binaries
Write-Verbose "Ready to copy files."
if(-not $Disable)
{
foreach ($file in $files)
{
Copy $file $Env:TF_BUILD_BINARIESDIRECTORY
}
Write-Verbose "Files copied."
}
A better solution would probably be to have 2 separate builds where the first build publishes the dependencies of the second project as a NuGet package. The Microsoft ALM Rangers have delivered a guide that explains how to set that up.
One option is to host your own nuget feed: http://docs.nuget.org/create/hosting-your-own-nuget-feeds
By hosting your own feed, you can execute custom build activities within your build process which updates your feed.
See this documentation for custom tfs build activities: http://nakedalm.com/creating-a-custom-activity-for-team-foundation-build/
See this documentation for adding powershell to your build process: http://blogs.technet.com/b/heyscriptingguy/archive/2014/04/21/powershell-and-tfs-the-basics-and-beyond.aspx
By hosting your own nuget feed, you have the ability to have your consuming solution leverage your private nugget feed and packages to deal with dependency management and versions. By leveraging the custom build activities you have the ability to update your nuget feed via .net or powershell. You also can automate the deployment via your powershell scripts.
I have some code that edits the registry, so it needs to run as admin. To do this, I start up a new PowerShell process from my running PowerShell script, and pass in part of the registry key path, which happens to be a version number, e.g. "12.0". The function in the new PowerShell process receives the string as "12" though, not "12.0", and so I'm getting errors that it can't find the registry key.
I've created a little sample powershell script that reproduces the problem. Here's the snippet:
$ScriptBlock = {
function Test([string]$VisualStudioVersion)
{
$VisualStudioVersion # This always displays 12, instead of 12.0
$Host.UI.RawUI.ReadKey()
}
}
# Run the script block's function.
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test(""12.0"")}"
Here I've hardcoded "12.0", but in practice I want to pass in a variable.
Any ideas on what I'm doing wrong? Thanks in advance.
Ok, after some experimenting the following seems to work correctly:
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test('12.0')}"
and it even works when using a variable:
$version = "12.0"
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test('$version')}"
I'm still not sure why using double quotes causes it to lose the precision and single quotes keeps it, but at least I solved my problem.
Update
So it turns out I'm a dummy and the problem was that I was using C# syntax of Test(""$version"") to call the function, instead of the proper PowerShell syntax Test ""version"". With this change it now works as expected.
I'm running into a problem with spaces in my parameters that I try to send into msdeploy from a powershell script.
There are a number of other related articles but none of them solve the problem.
Problems Using Power Shell And MSDeploy.
Similar SO issue that doesn't work: How to run exe in powershell with parameters with spaces and quotes
PowerShell BUG: Executing commands which require quotes and variables is practically impossible
Another SO issue that doesn't work:Passing parameters in PowerShell 2.0
The simplest example that succeeds and then fails when I make it more complicated is just dumping the default web site.
$msdeploy = "C:\Program Files\IIS\Microsoft Web Deploy\msdeploy.exe"
&$msdeploy -verb:dump -source:appHostConfig=`'default web site`' -verbose
==SUCCESS
This one?
$sitename="default web site"
&$msdeploy -verb:dump -source:appHostConfig=$sitename -verbose
==FAIL with the following error
msdeploy.exe : Error: Unrecognized argument '"-source:"appHostConfig=default'. All arguments must begin with "-".
At C:\xxx\test.ps1:122 char:6
+ &
+ CategoryInfo : NotSpecified: (Error: Unrecogn...begin with "-".:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Error count: 1.
The following variations have also failed
#FAIL
$sitename=`'default web site`'
$sitename=`'"default web site"`'
$sitename="`'default web site`'"
$sitename="default web site"
$sitename="'default web site'"
&$msdeploy -verb:dump "-source:appHostConfig=$sitename" -verbose
&$msdeploy -verb:dump -source:appHostConfig="$sitename" -verbose
&$msdeploy -verb:dump -source:appHostConfig='$sitename' -verbose
&$msdeploy -verb:dump -source:appHostConfig=`'$sitename`' -verbose
&$msdeploy -verb:dump -source:appHostConfig=`"$sitename`" -verbose
I'm at a loss. Everyone I work with is at a loss. Seriously this sucks. I loved Powershell. I loved msdeploy. I can't say that I love putting them together. It looks like it may have been easier to focus on the API instead of the cli.
EDIT:
The parameters in the string array suggested by Emperor XLII works well. An alternative solution is presented in the following article: The trials and tribulations of using MSDeploy with PowerShell
function PushToTarget([string]$server, [string]$remotePath, [string]$localPath) {
cmd.exe /C $("msdeploy.exe -verb:sync -source:contentPath=`"{0}`" -dest:computerName=`"{1}`",contentPath=`"{2}`" -whatif" -f $localPath, $server, $remotePath )
}
Using the technique from Keith's answer to How to run exe in powershell with parameters with spaces and quotes question you linked to, running echoargs -verb:dump -source:appHostConfig=$sitename -verbose gave me this output:
Arg 0 is <-verb:dump>
Arg 1 is <-source:appHostConfig=default>
Arg 2 is <web>
Arg 3 is <site>
Arg 4 is <-verbose>
This would explain the invalid argument of appHostConfig=default that msdeploy was seeing.
Running echoargs -verb:dump "-source:appHostConfig=$sitename" -verbose, with $sitename = "default web site", appears to result in the desired arguments:
Arg 0 is <-verb:dump>
Arg 1 is <-source:appHostConfig=default web site>
Arg 2 is <-verbose>
Though from your list, it appears that this did not work for you.
Another method you might try is building up the list of arguments in an array, which powershell can automatically escape. For example, this gives the same output as above:
[string[]]$msdeployArgs = #(
"-verb:dump",
"-source:appHostConfig=$sitename",
"-verbose"
)
echoargs $msdeployArgs
Just adding another way in case it is helpful to anyone:
Invoke-Expression "& '[path to msdeploy]\msdeploy.exe' --% -verb:sync -source:contentPath=`'$source`' -dest:contentPath=`'$dest`'"
"--%" is new to powershell 3. From here: "You simply add a the --% sequence (two dashes and a percent sign) anywhere in the command line and PowerShell will not try to parse the remainder of that line."
Found a working solution and easy fix.
Reference: http://answered.site/all-arguments-must-begin-with--at-cwindowsdtldownloadswebserviceswebservicesidservicepublishedwebsitesidservicedeploymentidservicewsdeployps123/4231580/
$msdeploy = "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe"
$msdeployArgs = #(
"-verb:sync",
"-source:iisApp='Default Web Site/HelloWorld'",
"-verbose",
"-dest:archiveDir='c:\temp1'"
)
Start-Process $msdeploy -NoNewWindow -ArgumentList $msdeployArgs
We had faced the similar kind of issue. Our fix was like below,
$path = "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe";
$verb = "-verb:sync";
$src = "-source:contentPath=[ESC][ESC][ESC]"c:\aa aa[ESC][ESC][ESC]";
$dest = "-dest:contentPath=[ESC][ESC][ESC]"c:\aa[ESC][ESC][ESC]";
Invoke-Expression "&'$path' $verb $src $dest";
where, ESC - is escape sequence/character
I tried every technique under the sun, and this is the only one that worked for me (using PowerShell 2).
cmd.exe /C $("msdeploy.exe -verb:sync -source:package=`"{0}`" -dest:auto,IncludeAcls=`"False`" -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -setParamFile:`"{1}`"" -f $mypackagepath, $myparamfilepath )
Here is another approach derived from the input below.
$msdeploy = "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe";
$command = "-verb:sync";
$sourcePath = "C:\aa aa\";
$source = $("-source:contentPath=`"{0}`"" -f $sourcePath);
$destPath = "C:\aa"
$destination = $("-dest:contentPath=`"{0}`" -f $destPath);
$msdeploycommand = $("`"{0}`" {1} {2} {3} -verbose" -f $msdeploy, $command, $source, $destination);
cmd.exe /C "`"$msdeploycommand`"";
This caters for the MSDeploy.exe being in its default installation folder which contains spaces. Hence the wrapping with the escape character (`).
I've used some ideas from answers above and came up with the following simpler function to do the thing.
Note that it is important to give the full path to MSDeploy as when running under the build agent it sometimes doesnt recognise the PATH to msdeploy.
function Deploy([string]$server, [string]$remotePath, [string]$localPath) {
$msdeploy = "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe";
cmd.exe /C $("`"{3}`" -verb:sync -source:contentPath=`"{0}`" -dest:computerName=`"{1}`",contentPath=`"{2}`" " -f $localPath, $server, $remotePath , $msdeploy )
}
Usage
Deploy $hostName $remotePath $source
All of the above did not work for me, this is the solution that worked:
# get msdeploy exe
$MSDeploy = ${env:ProgramFiles}, ${env:ProgramFiles(x86)} |
ForEach-Object {Get-ChildItem -Path $_ -Filter 'MSDeploy.exe' -Recurse} |
Sort-Object -Property #{Expression={[version]$_.VersionInfo.FileVersion}} -Descending |
Select-Object -First 1 -ExpandProperty FullName
#build deploy command
$deplyCmd = """""$MSDeploy"" -verb:sync -dest:iisApp=""Default Web Site"" -enableRule:DoNotDeleteRule -source:iisApp=""$ExtraWebFilesFolder"""
#execute
&cmd /c $deplyCmd
This problem has certainly been around for a long time and I spent some time battling it recently. The result has been successful for me so I'll post it here in hopes that it can help others who find this question in the future.
The first problem to resolve is getting rid of the spaces in the msdeploy path. There are two approaches here. One is persistent and requires you to have server access, the other is temporary in the context of your PowerShell script. Either will work but I'd prefer the first if it's an option for you.
For the first approach, create a junction point. Example script:
new-item -Path "c:\MS-WebDeploy" -ItemType Junction -Value "c:/Program Files (x86)/iis/microsoft web deploy v3"
For the second approach, create a PSDrive (w in this example)
New-PSDrive -Name "w" -PSProvider FileSystem -Root "C:/Program Files (x86)/iis/microsoft web deploy v3"
I'm using three PowerShell variables below. For example purposes, pretend that all three have spaces.
$ParamFilePath = "c:\deployment files\parameters.xml"
$PackageName = "c:\deployment files\My Website.zip"
$WebAppPath = "Default Web Site"
First, create an array and build up your arguments as needed.
#nothing needs to be done with these arguments so we'll start with them
[string[]]$arguments = #("-verb:sync", "-dest:auto", "-disableLink:AppPoolExtension", "-disableLink:ContentExtension", "-disableLink:CertificateExtension", "-allowUntrusted")
#double up on the quotes for these paths after the colon
$arguments += "-setParamFile:""$ParamFilePath"""
$arguments += "-source:package=""$PackageName"""
#must not have spaces with the commma, use single quotes on the name and value here
$arguments += "-setParam:name='IIS Web Application Name',value='$WebAppPath'"
#add your own logic for optional arguments
$arguments += "-EnableRule:EncryptWebConfig"
Now build the msdeploy command and put the PowerShell escape sequence to prevent PowerShell from "helping" later. Use the path you created with the junction or the PSDrive
$command = "w:\msdeploy.exe" + " --% " + $arguments -join " "
Finally, execute that command as a script block.
$sb = $ExecutionContext.InvokeCommand.NewScriptBlock($command)
& $sb
I've wrapped this and a bit more code into a script which is called like this.
.\Run-WebDeploy -WebAppPath "Default Web Site" -PackageName "c:\deployment files\My Website.zip" -ParamFilePath "c:\deployment files\parameters.xml" -EncryptWebConfig
In general, you can help yourself a lot by getting rid of the spaces in your paths/names. Sometimes, that can't be done and this should get you through.