Best way to execute powershell script ".ps1" [closed] - powershell

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
what is the best way to execute .ps1 script?
if I'm a user who has no idea about powershell, but I want to just double click the script and let it run.
could you please advise on the best practice?
Thank you
[CmdletBinding()]
Param(
[Parameter(Mandatory = $False,
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
HelpMessage = "Provide Files Path.")]
[string]$FilesPath = 'C:\Users\myusername\OneDrive - mycompany\mycompany\Sales & Marketing\Sales Reports',
[Parameter(Mandatory = $False,
ValueFromPipeline = $True,
ValueFromPipelineByPropertyName = $True,
HelpMessage = "Provide Sheet Name.")]
[string]$SheetName = 'Expenses'
)
Try
{
$Files = Get-ChildItem -Path $FilesPath -Include *.xlsx, *.xls, *.xlsm -Recurse
$Counter = $Files.Count
$Array = #()
$OutPutFilePath = (Join-Path $FilesPath -ChildPath "Exported-ExcelData.csv")
Remove-Item -Path $OutPutFilePath -Force -ErrorAction SilentlyContinue
ForEach ($File In $Files)
{
Write-Verbose -Message "Accessing File $($File.Name) and Exporting Data from Sheet $SheetName. Remaining $Counter Files." -Verbose
$Counter -= 1
$AllData = Import-Excel -Path $File.FullName -WorksheetName $SheetName -NoHeader
$i = 0
ForEach ($Data In $AllData)
{
$ArrayData = "" | Select-Object "P1", "P2", "P3", "P4", "P5", "P6"
$ArrayData.P1 = $Data[0].P1
$ArrayData.P2 = $Data[0].P2
$ArrayData.P3 = $Data[0].P3
$ArrayData.P4 = $File.Name
$ArrayData.P5 = $File.FullName
$ArrayData.P6 = ($i += 1)
$Array += $ArrayData
}
}
$Array | Export-Csv -Path $OutPutFilePath -Append -NoTypeInformation
}
Catch
{
$ErrorLog = "Error On " + (Get-Date) + ";$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
Write-Error "$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
}
Finally
{
Write-Host "Process has been completed!" -ForegroundColor Green
Read-Host "Press any key to continue..."
}

Create a batch file.
Launching your PS1 file can be problematic because of the Powershell policy and also, PS1 scripts won't launch by default on a windows machine.
However, if you create a batch file that reference your PS1 script, then you're all set.
The -ExecutionPolicy Bypass will ignore the current system policy so your script can run without issues.
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& 'C:\Users\SO\Desktop\YourScript.ps1'"
See below two batch command using relative path.
# Launch script
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& '%mypath:~0,-1%\data\install.ps1'"
# Same thing but force the script to run as admin
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -NoExit -File ""%mypath:~0,-1%\data\install.ps1""' -Verb RunAs}"
In these two variants, the PS1 I am lauching is in a subfolder called data and script is called install.ps1.
Therefore, I do not have to hardcode the script path in the batch file (you could put the PS1 at the same level than the batch file. In my example, I actually wanted only the batch at the first folder level and everything else "hidden from view" in a subfolder so the user does not have to think what file to execute.

I would recommend compiling the script, I believe that is the only way to reliably double-click a script. There are other ways, but they require weakening security, and partly as a result of this are hard to run across different systems.
The easiest way I've found to compile powershell is by using PowerGUI. You can paste your script and compile a portable .exe file that can be double-clicked. It doesn't require elevated permissions. Their website no longer hosts it, so you will need to find a 3rd party download.
You can also find a Visual Studio extension or one of Sapien's products, however both will cost money and aren't any better for just creating a basic double-clickable script.
Additionally, you will need to modify your script to include a prompt - assuming you wrote your code, that should be easily accomplished. Basically, you need to get it so that you never have to type anything in that you are not directly asked for, if you were to click RUN in ISE.

Related

Workflow not working when called by another script

I have a script, let's call it A.ps1 that uses a workflow called Copy-Parallel (the workflow is defined in the beginning of the script). I created a GUI.ps1 script that I use to easily select between multiple scripts, it calls the desired script with "Start-Process". In the end I created a shortcut, so I could run the GUI by double-click.
All resources are located on a server and the two scripts are in the same folder called Res. The shortcut is located in the same folder as Res. When we use the shortcut, the script works fine for me but other users get the: term 'Copy-Parallel' is not recognized as the name of a cmdlet, function... error.
We tried to run the A.ps1 script directly and it works fine for me and other users. We tried running the GUI.ps1 via a .bat file, the results were similar to the ones when we used the shortcut.
A.ps1:
workflow Copy-Parallel {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[array]
$Files,
[Parameter(Mandatory)]
[string]
$Destination
)
foreach -parallel -throttlelimit 20 ($File in $Files) {
Copy-Item -LiteralPath $File -Destination $Destination -Force
}
}
Copy-Parallel -Files $SourceFiles.FullName -Destination $DestinationPath
GUI.ps1 calls the script as follows:
$ScriptToRun = "$PSScriptRoot\A.ps1"
Start-Process PowerShell.exe -ArgumentList $ScriptToRun -NoNewWindow
The target for the shortcut that starts the GUI is: %windir%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -WindowStyle Hidden -NonInteractive -ExecutionPolicy bypass -File "Res\GUI.ps1"

Find and execute a file with powershell

I have to find and then execute a .exe file from a script deployed by our asset management software. Currently it looks like this:
Set-Location $PSScriptRoot
$proc = (Start-Process -FilePath "C:\Program Files (x86)\software\software name\Uninstall.exe" -ArgumentList "/S /qn" -Wait -PassThru)
$proc.WaitForExit()
$ExitCode = $proc.ExitCode
Exit($ExitCode)
As far as I understand the location for the location for the file is set and some users do not have it there hence why it fails.
So I understand that you can search for a program with
Get-ChildItem C:\Program Files (x86)\software\
And execute with Start-process -Filepath
But do I simply combine that with a | or is there an easier way/will it even work.
As commenter suggested, you can use Test-Path to test if a path exists:
$uninstallPath = Join-Path ${env:ProgramFiles(x86)} 'software\software name\Uninstall.exe'
if( Test-Path $uninstallPath ) {
$proc = Start-Process -FilePath $uninstallPath -ArgumentList '/S /qn' -Wait -PassThru
$proc.WaitForExit()
$ExitCode = $proc.ExitCode
Exit $ExitCode
}
I've also made the code more robust by avoiding the hardcoded "Program Files (x86)" directory, using an environment variable. Because of the parentheses in the name of the env var, it must be enclosed in curly braces.
For added robustness, you may read the path of the uninstall program from the registry, as detailed by this Q&A. If you are lucky, the program even stores a QuietUninstallString in the registry, which gives you the full command line for silent uninstall.

Why is the converted batch not running my powershell script? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I have a sample script for my diploma and it uses keypress mechanism, like so:
echo "A text prompting you to pres Enter"
$key = [System.Windows.Input.Key]::Enter
do
{
$isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)
if ($isCtrl)
{
$query = Get-Childitem 'D:\' -Recurse | Where-Object {$_.Name -match "controller.st$"}
foreach-object { $name = $query.FullName}
(Get-Content $name).Replace('A := AND55_OUT OR AND56_OUT OR AND61_OUT;', 'A := AND55_OUT') | Set-Content $name
#this actually does the replacing in a file
echo "sample text"
Start-Sleep -Seconds 30
echo "sample text"
break
}
} while ($true)
and the rest of the script continues.
I use the converting script
function Convert-PowerShellToBatch
{
param
(
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[string]
[Alias("FullName")]
$Path
)
process
{
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((Get-Content -Path $Path -Raw -Encoding UTF8)))
$newPath = [Io.Path]::ChangeExtension($Path, ".bat")
"#echo off`npowershell.exe -NoExit -encodedCommand $encoded" | Set-Content -Path $newPath -Encoding Ascii
}
}
Get-ChildItem -Path C:\path\to\powershell\scripts -Filter *.ps1 |
Convert-PowerShellToBatch
and I modify this to my case but when I run the batch file, I get the following error:
Unable to find type [System.Windows.Input.Keyboard].
At line:7 char:11
+ $isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Windows.Input.Keyboard:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
What can I do to overcome this?
tl;dr
As Jeroen Mostert notes in a comment, you need to load the assembly containing types System.Windows.Input.Key and System.Windows.Input.Keyboard into your script before you can use them, using Add-Type:
Add-Type -AssemblyName PresentationCore
echo "A text prompting you to press Enter"
$key = [System.Windows.Input.Key]::Enter
# ... the rest of your script.
The conversion script is nifty, but it cannot detect such problems.
Before converting, you should always run your script in a new session started with -NoProfile, so as to ensure that the script doesn't accidentally rely on side effects from the current session, such as the required assembly having been loaded by different code beforehand:
# Run the script in a pristine session to ensure that it is self-contained.
powershell -noprofile -file somescript.ps1
In particular, use of the ISE may have hidden the problem in your case, as these types are available in ISE sessions by default (because the ISE is built on WPF, which these types belong to) - unlike in regular console windows.
This is just one of the behavioral difference between the ISE and regular console windows], which makes the ISE - as convenient as it is - problematic.
Additionally, the ISE is no longer actively developed and cannot run PowerShell (Core) v6+ - see the bottom section of this answer for details.
Consider Visual Studio Code, combined with its PowerShell extension, as the actively maintained alternative.

Elevating PowerShell script permissions

I am trying to run script to manage some VHD Disks, but the disk mount is failing due to elevated permissions required. The user the script is run under is a local admin, but UAC is blocking it I think. The error which comes back is: “DiskState=Failed to mount disk - "Access to a CIM resource was not available to the client”
Ideally I need to the script to run under elevated command prompt automatically. Any idea's how I can achieve that programmatically?
The script I am running is this:
$location = "C:\temp"
$name = "downloadfile"
$Author = "FSLogix"
$FilePath = "Filepath here"
$LogFilePath = "Logfilepath here"
# Force to create a zip file
$ZipFile = "$location\$Name.zip"
New-Item $ZipFile -ItemType File -Force
$RepositoryZipUrl = "https://github.com/FSLogix/Invoke-FslShrinkDisk/archive/master.zip"
# download the zip
Write-Host 'Starting downloading the GitHub Repository'
Invoke-RestMethod -Uri $RepositoryZipUrl -OutFile $ZipFile
Write-Host 'Download finished'
#Extract Zip File
Write-Host 'Starting unzipping the GitHub Repository locally'
Expand-Archive -Path $ZipFile -DestinationPath $location -Force
Write-Host 'Unzip finished'
# remove the zip file
Remove-Item -Path $ZipFile -Force
# Run the FSLogix Optimisation
C:\temp\Invoke-FslShrinkDisk-master\Invoke-FslShrinkDisk.ps1 -Path $FilePath -Recurse -PassThru -LogFilePath $LogFilePath\logfile.csv
You can elevate the PS script using the Powershell as a separate process and make it "run as admin" like below:
start-process PowerShell -verb runas
OR
Powershell -Command "Start-Process PowerShell -Verb RunAs"
Apart from that , you can condition it as well. There is a beautiful conditional code shared by PGK which can help as well:
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
$arguments = "& '" +$myinvocation.mycommand.definition + "'"
Start-Process powershell -Verb runAs -ArgumentList $arguments
Break
}

Executing a file that may reside at two different locations using PowerShell

I have a cute little script in my $PROFILE helping me start Notepad++ from the script showing a file of my choosing.
function Edit{
param([string]$file = " ")
Start-Process "C:\Program Files\Notepad++\notepad++.exe" -ArgumentList $file
}
It's worked great until recently, where I jump between different systems. I discovered that NPP is installed in C:\Program Files on some systems but in C:\Program Files (x86) on others. I can edit the script adapting it but having done so a gazillion times (i.e. 5 to this point), I got sick and tired of it, realizing that I have to automate this insanity.
Knowing little about scripting, I wonder what I should Google for. Does best practice dictate using exception handling in such a case or is it more appropriate to go for conditional expressions?
According to Get-Host | Select-Object Version I'm running version 5.1, if it's of any significance. Perhaps there's an even neater method I'm unaware of? Relying on an environment variable? I'd also prefer to not use a method valid in an older version of PS, although working, if there's a more convenient approach in a later one. (And given my experience on the subject, I can't tell a duck from a goose.)
I would use conditionals for this one.
One option is to test the path directly if you know for certain it is in a particular location.
Hard coded paths:
function Edit{
param([string]$file = " ")
$32bit = "C:\Program Files (x86)\Notepad++\notepad++.exe"
$64bit = "C:\Program Files\Notepad++\notepad++.exe"
if (Test-Path $32bit) {Start-Process -FilePath $32bit -ArgumentList $file}
elseif (Test-Path $64bit) {Start-Process -FilePath $64bit -ArgumentList $file}
else {Write-Error -Exception "NotePad++ not found."}
}
Another option is pulling path information from registry keys, if they're available:
function Edit{
param([string]$file = " ")
$32bit = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Notepad++\' -ErrorAction SilentlyContinue).("(default)")
$64bit = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Notepad++\' -ErrorAction SilentlyContinue).("(default)")
if ($32bit) {Start-Process -FilePath "$32bit\notepad++.exe" -ArgumentList $file}
elseif ($64bit) {Start-Process -FilePath "$64bit\notepad++.exe" -ArgumentList $file}
else {Write-Error -Exception "NotePad++ not found."}
}
Based on the great help from #BoogaRoo (who should get some +1 for effort) and asked by the same to post my own version of the answer, I go against my reluctance to post asnwers to own questions due to strong sensation of tackiness.
My final version, taking into account systems that lack NP++ but still want to show the editor of some kind.
function Edit{
param([string]$file = " ")
$executable = "Notepad++\notepad++.exe"
$32bit = "C:\Program Files (x86)\" + $executable
$64bit = "C:\Program Files\" + $executable
$target = "notepad"
if(Test-Path $32bit) { $target = $32bit }
if(Test-Path $64bit) { $target = $64bit }
Start-Process $target -ArgumentList $file
}
Let me offer a streamlined version that also supports passing multiple files:
function Edit {
param(
# Allow passing multiple files, both with explicit array syntax (`,`-separated)
# or as indiv. arguments.
[Parameter(ValueFromRemainingArguments)]
[string[]] $File
)
# Construct the potential Notepad++ paths.
# Note: `-replace '$'` is a trick to append a string to each element
# of an array.
$exePaths = $env:ProgramFiles, ${env:ProgramFiles(x86)} -replace '$', '\Notepad++\notepad++.exe'
# See which one, if any, exists, using Get-Command.
$exeToUse = Get-Command -ErrorAction Ignore $exePaths | Select-Object -First 1
# Fall back to Notepad.
if (-not $exeToUse) { $exeToUse = 'notepad.exe' }
# Invoke whatever editor was found with the optional file(s).
# Note that both Notepad++ and NotePad can be invoked directly
# without blocking subsequent commands, so there is no need for `Start-Process`,
# whose argument processing is buggy.
& $exeToUse $File
}
An array of potential executable paths is passed to Get-Command, which returns a command-info object for each actual executable found, if any.
-ErrorAction Ignore quietly ignores any errors.
Select-Object -First 1 extracts the first command-info object, if present, from the Get-Command output; this is necessary to guard against the (perhaps unlikely) case where the executable exists in both locations.
$exeToUse receives $null (effectively) if Get-Command produces no output, in which case Boolean expression -not $exeToUse evaluates to $true, causing the fallback to notepad.exe to take effect.
Both command names (strings) and command-info objects (instances of System.Management.Automation.CommandInfo or derived classes, as returned by Get-Command) can be executed via &, the call operator.