PowerShell get directory of a program - powershell

I recently wrote a script and want to share it with my colleagues. It’s a simple copy and paste program that creates log-files after each run. The problem is that I used this: start transcript -Path C:\Users…
The program works fine but if anyone else runs the script it won’t be able to create log-files, since the directory is a copy of mine.
Now to my question: Is there anyway that the program can find out the directory where each user saved the script so it can create a sub-folder in that directory and then dump the logs in there?
Thank you in advance

The path to the folder containing the currently executing script can be obtained through the $PSScriptRoot automatic variable:
Start-Transcript -OutputDirectory $PSScriptRoot

Here's how I record PowerShell sessions using the Start-Transcript cmdlet. It creates a log file, where the script is run from.
#Log File Paths
$Path = Get-Location
$Path = $Path.ToString()
$Date = Get-Date -Format "yyyy-MM-dd-hh-mm-ss"
$Post = "\" + (Get-Date -Format "yyyy-MM-dd-hh-mm-ss") + "-test.log"
$PostLog = $Path + $Post
$PostLog = $PostLog.ToString()
#Start Transcript
Start-Transcript -Path $PostLog -NoClobber -IncludeInvocationHeader
#Your Script
Get-Date
#End Transcript
Stop-Transcript -ErrorAction Ignore

Related

Powershell script with embedded exe

Is there a way to embed an exe to an existing powershell script? My boss wants me to come up with a way to install software on our employees computers that work from home and aren't tech savvy. Essentially, I need to copy a file locally to their computer (which is an exe) and run it from within powershell (or command line) terminal with some arguments (i.e., /norestart /quiet, etc).
You can use Base64 encoding to embed an exe in a PowerShell script. Run this script to encode the exe. It produces 'base64Decoder.ps1' in the Downloads folder.
# Requires PowerShell 5.1
# Run this script to encode the exe.
# It produces 'base64Decoder.ps1' in the Downloads folder.
$folder = "$env:UserProfile\Downloads\Demo\"
$file = "PowerShell-7.0.0-win-x64.msi"
$option = [System.Base64FormattingOptions]::InsertLineBreaks
$path = Join-Path -Path $folder -ChildPath $file
$bytes = Get-Content $path -Encoding Byte -ReadCount 0
$outputProgram = [System.Text.StringBuilder]::new()
[void]$outputProgram.AppendLine( '$encodedText = #"' )
[void]$outputProgram.AppendLine( ([Convert]::ToBase64String($bytes, $option)) )
[void]$outputProgram.AppendLine( '"#' )
[void]$outputProgram.Append(
#"
`$downloads = Join-Path -Path `$Env:USERPROFILE -ChildPath "Downloads"
`$file = "$file"
`$path = Join-Path -Path `$downloads -ChildPath `$file
`$value = [System.Convert]::FromBase64String(`$encodedText)
Set-Content -Path `$path -Value `$value -Encoding Byte
"#
)
$downloads = Join-Path -Path $Env:USERPROFILE -ChildPath "Downloads"
$outFile = "base64Decoder.ps1"
$outPath = Join-Path -Path $downloads -ChildPath $outFile
Set-Content -Path $outPath -Value ($outputProgram.ToString())
You can copy and paste the contents of base64Decoder.ps1 into an existing PowerShell script to embed the exe. Or, if too large, include base64Decoder.ps1 with the original script and invoke it when necessary.
Run the script on the target computer to reproduce the original file in the Downloads folder. This is valid PowerShell syntax and can be included in a script.
& "$env:UserProfile\Downloads\base64Decoder.ps1"
You might have to set the execution policy before the script will run.
Set-ExecutionPolicy RemoteSigned
Invoke the exe with Start-Process. This can be saved in a script.
Start-Process -FilePath "$env:UserProfile\Downloads\PowerShell-7.0.0-win-x64.msi" -ArgumentList '/? '
If you want to send a PowerShell script via E-mail, attach it as .txt and have them rename it. I'm sure you know that file attachments are generally limited to 10MB.
If the exe is available online, you can use Invoke-WebRequest which is much easier.
Invoke-WebRequest "https://github.com/PowerShell/PowerShell/releases/download/v7.0.0/PowerShell-7.0.0-win-x64.msi" -outfile "$env:UserProfile\Downloads\PowerShell-7.0.0-win-x64.msi"
You can test these steps in Windows Sandbox.
While this is the technically correct answer to your question, I don't recommend it.
First, it is more complicated than simply downloading an installer from the Internet and using (the MSI) switches on it.
Second, the performance of my script is poor for nontrivial exe's. And it will create more problems than it solves.
I'm not sure what the assumption is here. But if these computers are not managed, I imagine there will be a support request for each install. What you won't be able to do is just E-mail this script to 100 people or put it in a logon script and walk away. That would be very bad. Even if this were in the office, I would not deploy an unattended install without thorough testing. And that's assuming local storage and a logon script or equivalent: not people working from home as a one-off.

Install an .MSI file from the same directory as the powershell script without setting a static file path

I have a script that we use that is in Powershell however I need the script to be able to find the files that it needs to install an application dynamically as users can copy the folder to any folder on their computer. Currently I have the below code set, I guess my question is, if the script is in the same folder as the install files. How do I tell powershell to just look in the directory that its being ran from for the install files?
$Install = "C:\Other Folder\File.msi"
$InstallArg = "/quite /norestart"
Start-Process '
-FilePath $Install '
-ArgumentList $InstallArg '
-PassThru | Wait-Process
Any help would be appreciated. Thank you.
Update, I found that I have to be in the directory the script is in. However since we have to run ISE with admin credentials it automatically defaults to C:\Windows\System32 as the directory powershell is looking in regardless if I tell it to open the script. If that is the case how can I tell it to look where the script is located so that it can find the files that it needs?
I have found my answer below is how I got it to work with our current situation. Thank you Thomas for the help!
$ScriptLocation = Get-ChildItem -Path C:\Users -Filter Untitled2.ps1 -Recurse | Select Directory
cd $ScriptLocation.Directory
$Install = ".\Install.msi"
$InstallArg = "/quite /norestart"
Start-Process '
-FilePath $Install '
-ArgumentList $InstallArg '
-PassThru | Wait-Process
Define a relative path:
$Install = ".\File.msi"
If you are not running the script inside the directory, where the script itself and the executable are stored, you will have to determine the absolute path to it. With PowerShell 3+ you can easily determine the directory, where your script is stored, using the $PSScriptRoot variable. You could define your $Install variable like this:
$Install = Join-Path -Path $PSScriptRoot -ChildPath "File.msi"
Thank you so much for your help everyone! I accidentally stumbled upon the perfect solution.
# Set active path to script-location:
$path = $MyInvocation.MyCommand.Path
if (!$path) {
$path = $psISE.CurrentFile.Fullpath
}
if ($path) {
$path = Split-Path $path -Parent
}
Set-Location $path

How to access specific script file in a server Powershell instance?

I have a script file script1.ps1 that is passing an argument to another script file script2.ps1 in a psexec.exe command, the problem is that when i run this file in my system, it executes perfectly, but when running it by a server, it says the file path does not exist. Here is the code
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$path = $dir + '\Script2\script2.ps1'
$args = 1
$Query = 'psexec.exe \\' + $ip + ' /accepteula cmd /c "powershell -noninteractive -file $path -id $args"'
Invoke-Expression $Query
So sorry that i currently don't have screenshot of the error message, but it is saying file does not exist. I want to know is there an special way to declare a file path? I have tried this as well, \\$computer1\C$\Folder\Script2\script2.ps1 but doesn't work, seems like i am doing something wrong in it.

Create folder using cmd and passing formatted date from powershell

I would like to create folder where it's name is date in specific format. Date format is from powershell echo result. How could I combine this two things?
cmd.exe /c md \"%1\%%"date%%\"" /f and powershell get-date -format "{yyyy-MMM-dd HHmm}?
It must be in one line and starts with cmd.
I will appreciate for any help.
This should do what you are looking in powershell:
$format=get-date -format "yyyy-MMM-dd HHmm"
New-Item -ItemType Directory -Path "C:\Folder\$format"
This is the one liner from the cmd prompt:
Powershell -command "New-Item -Name (get-date -format 'yyyy-MMM-dd HHmm') -ItemType Directory -Path 'C:\Folder' -Force"
Hope it helps.
If you want to run it in a single line in the cmd, try this. This will still just powershell to run. But you can run it from a bat file,
powershell $date = get-date -format "{yyyy-MMM-dd HHmm}; md c:\temp\$date
Else you can run it in powershell
$date = get-date -format "{yyyy-MMM-dd HHmm}"
New-Item -ItemType Directory -Path "c:\temp\$date"
Hope this helps.
/Anders
If you're still wanting a right-click context menu entry, (which creates a directory named using the format you've provided), you can do so by just entering this single line into a Command prompt window.
Powershell -NoP -C "New-Item 'HKCU:\Software\Classes\Directory\Background\shell\MkStampedDir' -Value 'NOW Folder Here' -Force"; "New-Item 'HKCU:\Software\Classes\Directory\Background\shell\MkStampedDir\command' -Value '\"C:\Windows\system32\WindowsPowerShell\v1.0\Powershell.exe\" -NoP -C \"$dt = get-date -f {yyyy-MMM-dd HHmm}; MD \"\"%V\"\"\$dt\"\"\"' -Force"
This should create a context menu entry named NOW Folder Here, seen when you right click in an empty area of an explorer window, (and probably the desktop). When the entry is selected a powershell command should run creating the directory in that folder.

Start-Transcript and Logging Batch File Output

I have a function in a PowerShell module which creates a log file and starts a transcript using that file (see below). When running a PowerShell script, this works great and captures all output.
When running a PowerShell script which calls a batch file (which we do quite often while migrating from CMD > PowerShell), the batch file output shows on the console in the same window as the PowerShell script, but the transcript log file shows only 1 blank line where the call to the batch file is.
09:53:25 AM [Success] Zip file already up to date, no need to download!
09:53:25 AM [Note ] Calling 1.bat
10:07:55 AM [Note ] Calling 2.bat
I'm calling the batch files from .ps1 scripts with only the ampersand '&'.
What's strange is that sometimes the batch file output is captured in the log (usually the first batch file called). However I can't find anything special about these files.
What's also strange is that sometimes we call external programs (WinSCP) and the output from those commands only sometimes show in the transcript. Possibly relevant.
For reference, here is the function I use to create a transcript of our processes.
Function Log_Begin()
{
<#
.SYNOPSIS
Starts the process for logging a PowerShell script.
.DESCRIPTION
Starts the process for logging a PowerShell script. This means that whenever
this function is called from a PowerShell script, a folder called 'Logs' will
be created in the same folder, containing a full transcript of the script's output.
.EXAMPLE
C:\PS> Log_Begin
#>
Process
{
$ScriptLoc = $MyInvocation.PSCommandPath
$WorkDir = Split-Path $ScriptLoc
If (!(Test-Path "$WorkDir\Logs")) {mkdir "$WorkDir\Logs" | Out-Null}
$LogPath = "$WorkDir\Logs"
$ScriptName = [io.path]::GetFileNameWithoutExtension($ScriptLoc)
$LogDate = Get-Date -format "yyyy-MM-dd"
$LogName = "$ScriptName $LogDate.log"
$global:Log = $LogPath + "\" + $LogName
$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | out-null
$ErrorActionPreference = "Continue"
# Create file and start logging
If (!(Test-Path $Log)) {
New-Item -Path $Log -ItemType File | Out-Null
}
Start-Transcript -Path $Log -Append
}
}
Does anyone have any ideas on how I can capture the batch file output? Preferably I wouldn't have to change every call to a batch file from the script, and make something in the module.