Start-Transcript and Logging Batch File Output - powershell

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.

Related

Convert PowerShell script importing within other PowerShell script to EXE [duplicate]

$password = ConvertTo-SecureString “Password+++” -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ("Admin", $password)
$FileLocale = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
Write-Output $FileLocale
$AntFile = "$FileLocale\StartApps.ps1"
Write-Output $AntFile
Start-Process PowerShell.exe -ArgumentList "-command &$AntFile -UserCredential $Cred"
Hi, that code works in .ps1, I call the other script, and he makes his job. But when I transform it in .exe with the help of ps2exe, he doesn't do his job anymore. As admin or not. It's not the first time I use that start-process, but it's the first time I use a variable as a target for the command. Do anyone know what go wrong between the ps1 and exe ?
Thanks
While an executable compiled with ps2exe uses a .ps1 file as input, at runtime no actual .ps1 file is involved, which is why PowerShell's command-reflection variables cannot tell you anything about a running script file.
When running an actual .ps1 file, you'd use the following about_Automatic_Variables:
$PSCommandPath contains the the executing script file's full file path.
$PSScriptRoot contains the script file's full directory path (i.e. the full path of the directory in which the script file is located).
In a ps2exe-compiled executable (.exe), where these variables have no values, you can use the following instead:
[Environment]::GetCommandLineArgs()[0] contains the executable file's full file path.
Split-Path -LiteralPath ([Environment]::GetCommandLineArgs()[0]) contains the executable file's full directory path.
Applied to your code - assuming that a separate StartApp.ps1 file is present alongside your .exe file:[1]
$FileLocale = Split-Path -LiteralPath ([Environment]::GetCommandLineArgs()[0])
$AntFile = Join-Path $FileLocale StartApps.ps1
If you want to make your code work in both invocation scenarios - the original .ps1 file directly as well as the compiled .exe file - use the following:
$FileLocale =
if ($PSScriptRoot) { $PSScriptRoot }
else { Split-Path -LiteralPath ([Environment]::GetCommandLineArgs()[0]) }
$AntFile = Join-Path $FileLocale StartApps.ps1
[1] Note that at runtime no information is available about where the original .ps1 file that served as compile-time input was originally located - only that file's content becomes part of the .exe file.

PowerShell get directory of a program

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

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.

Powershell robocopy logging

How can you log to a text file only new files that have been robocopied from source to destination. I've tried the robocopy /LOG:file command however that logs everything
You can use the Out-File function in powershell in order to output logs to text file instead of outputting them in the shell.
$execution = #some robocopy function to run
$execution| Out-File -LiteralPath "your_path"

How powershell handles returns from & calls

We have field devices that we decided to use a powershell script to help us handle 'updates' in the future. It runs every 5 minutes to execute rsync to see if it should download any new files. The script, if it sees any file types of .ps1, .exe, .bat ,etc. will then attempt to execute those files using the & operator. At the conclusion of execution, the script will write the file executed an excludes file (so that rsync will not download again) and remove the file. My problem is that the return from the executed code (called by &) behaves differently, depending on how the main script is called.
This is the main 'guts' of the script:
Get-ChildItem -Path $ScriptDir\Installs\* -Include #("*.ps1","*.exe","*.cmd","*.VBS","*.MSI") | ForEach {
Write-Verbose "Executing: $_"
& $_
$CommandName = Split-Path -Leaf $_
Write-Verbose "Adding $CommandName to rsync excludes"
Write-Output "$CommandName" | Out-File -FilePath $ScriptDir\excludes -Append -Encoding ASCII
Write-Verbose "Deleting '$_'"
Remove-Item $_
}
When invoking powershell (powershell.exe -ExecutionPolicy bypass) and then executing the script (.\Update.ps1 -Verbose), the script runs perfectly (i.e. the file is written to excludes and deleted) and you can see the verbose output (writing and deleting).
If you run the following (similar to task scheduler) powershell.exe -ExecutionPolicy bypass -NoProfile -File "C:\Update.ps1" -Verbose, you can see the new script get executed but none of the steps afterwards will execute (i.e. no adding to excludes or removing the file or the verbose outputs).