Run python-script as terminal program (without .py-file-ending) from anywhere on Windows Server 2016 - python-3.x

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.

Related

What is the windows equivalent of Linux command wc -l?

I have a piece of code that is meant to send the following to the linux command line:
wc -l C:/inputdirectory/P*
However, I need to run this script in Windows, and am trying to find the equivalent command. I have tried
find /c /v C:/inputdirectory/P*
But this throws an error, that /v is not a valid command. Can you please tell me why this isn't working?
*note, the command itself doesn't say "inputdirectory", it has the correct directory, it's just too tedious and private to type out
Courtesy of Eryk Sun:
Try searching for "", i.e. an empty string; use only backslash as the path separator; and quote the path if it has spaces in it:
find /c /v "" "C:\inputdirectory\P*"
From cmd.exe (the Command Prompt / a batch file), which is obsolescent:
Use the accepted answer.
From PowerShell, you have two options:
Option A (suboptimal): Use the accepted answer too, with a small tweak:
find --% /c /v "" "C:\inputdirectory\P*"
Note: --%, the stop-parsing symbol, tells PowerShell to pass subsequent arguments through as-is to the target program (after expanding cmd-style environment-variable references such as %USERNAME%, if any).
In the case at hand, this prevents PowerShell from parsing "" and - mistakenly - neglecting to pass it through to the external target program (find.exe).
For a summary of PowerShell's highly problematic handling of quotes when calling external programs, see this answer.
Output from the above find.exe command - and, indeed, any external program, is just text, and in this case it looks something like this:
---------- PFILE1.TXT: 42
---------- PFILE2.TXT: 666
...
While this output is easy to grasp for a human observer, it makes subsequent programmatic processing cumbersome, because text parsing is required.
Using a PowerShell-native command (cmdlet), as described below, offers more flexibility, because PowerShell commands typically emit objects with typed properties, which greatly facilitates subsequent processing.
Option B (preferred): Use PowerShell's own Measure-Object cmdlet with the -Line switch:
Note: While this command is more verbose than the find solution, it ultimately offers more flexibility due to outputting objects with typed properties, which greatly facilitates subsequent processing; additionally, PowerShell's sophisticated output-formatting system offers user-friendly default representations.
Get-Item -Path "C:\inputdirectory\P*" -PipelineVariable file | ForEach-Object {
Get-Content -LiteralPath $file |
Measure-Object -Line |
Select-Object #{ Name='File'; Expression={ $file } }, Lines
}
The above outputs objects that have a .File and .Lines property each, which PowerShell prints as follows by default:
File Lines
---- -----
C:\inputdirectory\Pfile1.txt 42
C:\inputdirectory\Pfile2.txt 666
...
In addition to a nicer presentation of the output, the object-oriented nature of the output makes it easy to programmatically process the results.
For instance, if you wanted to limit the output to those files whose line count is 100 or greater, pipe to the following Where-Object call to the above command:
... | Where-Object Lines -ge 100
If you (additionally) wanted to sort by highest line count first, pipe to the Sort-Object cmdlet:
... | Sort-Object -Descending Lines
How can I count the lines in a set of files?
Use the following batch file (CountLines.cmd):
#echo off
Setlocal EnableDelayedExpansion
for /f "usebackq" %%a in (`dir /b %1`) do (
for /f "usebackq" %%b in (`type %%a ^| find "" /v /c`) do (
set /a lines += %%b
)
)
echo %lines%
endlocal
Usage:
CountLines C:/inputdirectory/P*
Further Reading
An A-Z Index of the Windows CMD command line - An excellent reference for all things Windows cmd line related.
dir - Display a list of files and subfolders.
find - Search for a text string in a file & display all the lines where it is found.
for /f - Loop command against the results of another command.

'Undefined variable' error when running tcsh script

I am trying to execute each script that has "~SAC" at the end of their filenames. For such purpose, I wrote tc shell script using "foreach" like the code below.
However, when I execute it by typing ./run.tcsh, it only shows that
"tmpdir: Undefined variable". ('tmpdir' is the directory which has all the files which I want to apply my code)
How could I resolve this problem?
#!/bin/tcsh
set inputdir=tmpdir
foreach sacfile(`find $tmpdir -name '*.SAC'`)
echo "processing $sacfile"
classic_LR $sacfile
end
The name of my execution file is 'classic_LR' and it is executed by following form of input;
./classic_LR sacfile
where 'sacfile' refers to the name of file I want to apply classic_LR.
Your variable is inputdir and not tmpdir.
So, change
foreach sacfile(`find $tmpdir -name '*.SAC'`)
to
foreach sacfile(`find $inputdir -name '*.SAC'`)

workon command doesn't work in Windows PowerShell to activate virtualenv

I have installed virtualenvwrapper-win and when I try this command
workon <envname>
In CMD it works, but not in Windows PowerShell.
In Windows PowerShell I have to do Scripts\activate.ps1 and then I get the envname before the prompt.
Can you please let me know how can I make workon command working in PowerShell?
workon is a batch script. If you run it from PowerShell it's launched in a new CMD child process, doing its thing there, then exit and return to the PowerShell prompt. Since child processes can't modify their parent you lose all modifications made by workon.bat when you return to PowerShell.
You basically have two options:
Rewrite workon.bat (and the other batch scripts it calls) in PowerShell.
Run workon.bat in without exiting from the CMD child process:
& cmd /k workon <envname>
If you just want a shortcut workon that you can call directly from PowerShell you can wrap that commandline in a function and put the function definition into your PowerShell profile:
function workon($environment) {
& cmd /k workon.bat $environment
}
Use the scriptname with extension here to avoid infinite recursion.
The answer by Ansgar Wiechers technically works but it uses cmd which means you are basically using the cmd prompt from within PowerShell and you lose the additional functionality provided by PowerShell. You can modify the function above to the following:
function workon ($env) {
& .\Envs\$env\Scripts\activate.ps1
}
This will allow you to continue to use PowerShell commands (that do not work in cmd such as ls) in your virtual environment
This also assumes that your environments are saved in .\Envs. If they are elsewhere, then adjust the path in the function accordingly, or set the WORKON_HOME environment variable, see below.
If you have set the WORKON_HOME environment variable (which you should !), you can instead use:
function workon ($env) {
& $env:WORKON_HOME\$env\Scripts\activate.ps1
}
Additionally, if you are not a Windows user (like me) and need help on where to put that function and how to get it to load when you open PowerShell. Here are some additional resources that helped me:
Background:
How to Write a PowerShell Script Module
Importing a PowerShell Module
How to get PowerShell to autoload your module when it starts:
How to Create a PowerShell Profile
Start PowerShell with modules loaded
just type "CMD" on powershell and it will bring up cmd and then workon
There is a much simpler solution! Just go to your python scripts folder, where the workon.bat file exists and create a new file named workon.ps1 and add the following line to it
iex ("~\Envs\" + $args[0] + "\Scripts\activate.ps1")
You many need to change this appropriately if you store your virtualenvs elsewhere and also set the Execution Policy to allow scripts. Now you can use workon in both cmd and powershell, since the ps1 will be executed in powershell and bat in cmd.
You can also check out my fork (full disclosure: I'm the author of the powershell part) of virtualenvwrapper-win, which contains some rewritten scripts for powershell and should work on both CMD and powershell. If you want to copy-paste, create two files, workon.ps and `cdproject.
workon.ps1:
if (-not (Test-Path env:WORKON_HOME))
{
$WORKON_HOME = '~\Envs'
} else {
$WORKON_HOME = ($env:WORKON_HOME).Replace('"','')
}
if (-not (Test-Path env:VIRTUALENVWRAPPER_PROJECT_FILENAME)) {
$VIRTUALENVWRAPPER_PROJECT_FILENAME = '.project'
} else {
$VIRTUALENVWRAPPER_PROJECT_FILENAME = ($env:VIRTUALENVWRAPPER_PROJECT_FILENAME).Replace('"','')
}
if ($args.length -eq 0) {
echo "Pass a name to activate one of the following virtualenvs:"
echo ==============================================================================
(Get-ChildItem -Path $WORKON_HOME).Name
return
}
$VENV = $args[0]
if (!(Test-Path -Path ("$($WORKON_HOME)\$($VENV)"))) {
echo ("virtualenv $($VENV) does not exist")
echo "Create it with 'mkvirtualenv $($VENV)'"
return
}
if (!(Test-Path -Path ("$($WORKON_HOME)\$($VENV)\Scripts\activate.ps1") )) {
echo "$($WORKON_HOME)$($VENV)"
echo "doesn't contain a virtualenv (yet)."
echo "Create it with 'mkvirtualenv $($VENV)'"
return
}
iex ("$($WORKON_HOME)\$($VENV)\Scripts\activate.ps1")
if (Test-Path -Path ("$($WORKON_HOME)\$($VENV)\$($VIRTUALENVWRAPPER_PROJECT_FILENAME)")) {
iex "cdproject"
}
cdproject.ps1:
function Show-Usage {
echo ""
echo "switches to the project dir of the activated virtualenv"
}
if (-not (Test-Path env:VIRTUAL_ENV)) {
echo ""
echo "a virtualenv must be activated"
Show-Usage
return
}
if (-not (Test-Path env:VIRTUALENVWRAPPER_PROJECT_FILENAME)) {
$VIRTUALENVWRAPPER_PROJECT_FILENAME = '.project'
} else {
$VIRTUALENVWRAPPER_PROJECT_FILENAME = ($env:VIRTUALENVWRAPPER_PROJECT_FILENAME).Replace('"','')
}
if (-not (Test-Path "$($env:VIRTUAL_ENV)\$($VIRTUALENVWRAPPER_PROJECT_FILENAME)")) {
echo ""
echo "No project directory found for current virtualenv"
Show-Usage
return
}
$ENVPRJDIR = Get-Content "$($env:VIRTUAL_ENV)\$($VIRTUALENVWRAPPER_PROJECT_FILENAME)" -First 1
# If path extracted from file contains env variables, the system will not find the path.
# TODO: Add this functionality
cd $ENVPRJDIR
I encountered the same problem using virtualenvwrapper in powershell on Windows10. I like the answer by #Erock but that overwrrides the workon in such a way that running workon without an argument throws error instead of showing available environments. Here is my solution.
function workon ($env) {
if ($env) {
& $env:WORKON_HOME\$env\Scripts\activate.ps1
} else {
Write-Host "Pass a name to activate one of the following virtualenvs:"
Write-Host" ================================================================"
Get-ChildItem $env:WORKON_HOME -Name
}
}
Note that I had already set WORKON_HOME envrionment variable.
I had the similar issue.
Fixed it by following some of the above steps.
Listing it down here:
install both VirtualEnv and VirtualEnvWrapper-win
Set the ExecutionPolicy to RemoteSigned Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Add environment Path 'WORKON' to 'envs directory'
Now try 'workon' command
installing:
{ pip install virtualenvwrapper-win }
Note that the commands at default can only be used in CMD.
Modify to use in the powershell:
function workon ($env) {
& .\Envs\$env\Scripts\activate.ps1
}
Creating a virtual environment:
{ workon venv }
or any other name
Activating:
{ . venv\scripts\activate }
To see a list of created virtual environments:
{ workon }
To deactivate:
{ deactivate }

Will ailas be dropped in xterm after Linux login?

I'm using tcsh in red hat with a XFCE client. I want to make some setting in my .cshrc be execute only one time. I'm using the following command but it seems strange to me. The LOAD_SETTING env was reported as set when I start a xterm but the alias in the setting cannot be found. It seems my .cshrc was sourced at login time (I got the LOAD_SETTING environment variable set ) but some setting (at least alias) are not inherited. My question is which setting will be loaded and which will be lost in the terminal when I just start the terminal just after login?
I tried to change 'setenv LOAD_SETTING 3' to 'set LOAD_SETTING=3' and it seems works as expected: setting will be loaded when I start the terminal at first time but I just worried about the NOT environment variable is not safe enough as I'm trying to avoid SGE source my .cshrc for multi times.
if ! $?LOAD_SETTING then
echo "loading "`pwd`"/project_setting.csh"
source /project/project_setup
#force useful path order
set path = (/tools/bin/x86_64/ /tools/cad/bin /sf/tools/cad/scripts /oge/sfgrid/6.2u5p1/wrapper /oge/sfgrid/6.2u5p1/bin/lx26-amd64 . /bin /sbin /usr/bin /usr/sbin /sf/tools/bin /sf/tools/cad/wrapper/bin $path)
#delete repeated path
set path=(`echo $path | perl -e 'print join(" ", grep { not $seen{$_}++ } split(/ /, scalar <>))'`)
alias greg 'set ret=`find_tot` && $ret/bin/greg'
setenv LOAD_SETTING 3
exit 1
else
echo "project_setting.csh has already been loaded, abort to load it again!"
exit 1
endif

How do you call msdeploy from powershell when the parameters have spaces?

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.

Resources