Get the last command executed on a PowerShell script - powershell

I'm updating the logging function I use for all my scripts and making it compatible with the SCCM logging viewer.
Under the fields that the SCCM viewer takes is the component field, and I'll like to fill it with the main command that it's run and I'm logging. For example, if I'm logging "Data copied" and I did that with Copy-Item, I'd like that the logs says Copy-Item for that line of the log.
So far I've tested the following methods, using $$ and the history cmdlets (Get-History, Add-, etc.), but they only work with the PowerShell console, not with script execution.
The final result I'd like to get is:

I think this is what you're talking about?
$Folder = "some path"
$Cmdline = 'Remove-Item $Folder -Force -Recurse'
Invoke-Expression $Cmdline
Write-Host $Cmdline.replace("`$Folder",$Folder)

Related

PowerShell function not running as expected

I have a curious case that I cannot fathom the reason for...
Please know I am a novice to PowerShell.
I am working on a PowerShell menu system to help automate building out new computers in my environment. I have a PS1 file that holds the script for an app install. When I use the script to reference this I am able to run it and have no issue. However, when I try inserting this into a function and referencing it does not.
This works:
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
# {installMS32Bit}
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
Start-Sleep -seconds 2
}
This does not:
function installMS32Bit(){
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
}
}
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
{installMS32Bit}
Start-Sleep -seconds 2}
install.ps1 file:
# Copy MS Office uninstall and setup to local then run and install 32-bit Office
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\setup.exe' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\uninstall.xml' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\32-bit\Setup.exe' -Destination 'C:\temp' -Force
Invoke-Expression ("cmd /c 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'")
Start-Process -FilePath 'C:\temp\Setup.exe'
Secondary question and a little explanation for Invoke-Expression...
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
If there is a better way to do this in PowerShell I am all ears!
{installMS32Bit}
As Mathias points out in a comment on the question, this statement doesn't call your function, it wraps it in a script block ({ ... })[1], which is a piece of reusable code (like a function pointer, loosely speaking), for later execution via &, the call (execute) operator.
To call your function, just use its name (by itself here, given that there are no arguments to pass): installMS32Bit
Invoke-Expression should generally be avoided; definitely don't use it to invoke an external program, as in your attempts.
Additionally, there's generally no need to call an external program via cmd.exe (cmd /c ...), just invoke it directly.
For instance, replace the last Invoke-Epression call from your question with:
# If the EXE path weren't quoted, you wouldn't need the &
& 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
(On Windows), Start-Process by default executes a console application in a new window (unless you specify -NoNewWindow), asynchronously (unless you specify -Wait).
You cannot pass a .ps1 script directly to Start-Process (it will be treated like a document to open rather than an executable to call), but you can pass it to PowerShell's CLI via the -File parameter:
Start-Process powershell.exe '-File install.ps1'
The above is short for:
Start-Process -FilePath powershell.exe -ArgumentList '-File install.ps1'
That is, PowerShell will execute the following in a new window:
powershell.exe -File install.ps1
[1] Since you're not assigning the script block being created to a variable, it is implicitly output (printed to the display, in the absence of a redirection); a script block stringifies by its literal contents, excluding the enclosing { and }, so string installMS32Bit will print to the display.

Why working script fails when running in the background?

I have such script (here simplified):
while ($true)
{
Write-Host "Looping..."
Write-Host "Looping..." 6>> trash.txt
Start-Sleep -s 1
}
when I run it directly it works, but when I run it in the background:
Start-Job { .\sleeper.ps1 }
for a second it is seen as Running but shortly after as Failed and indeed file "trash.txt" is not created at all, so even one iteration is not executed.
What is wrong here?
I think the main issue is around the $PWD and -FilePath param, but I will list some info on Write-Host too:
Start-Job should be run with the -FilePath parameter. This is because {} is a ScriptBlock object, which is by default taken by the -ScriptBlock parameter, which you do not want. Example solution to that line: Start-Job -FilePath ./script.ps1
The $PWD or present working directory is, by default, the home directory / user profile of the current user when executed in a PowerShell job (Linux: $HOME // Windows: $Home/Documents). You can test this by executing a job simply with that variable (it may be $ENV:PWD on Windows?). Either way, it is likely not the same as the directory you are executing this in. trash.txt is being made and appended to, but it is being made in a different directory than your current directory. You will want your script to explicitly include an absolute path to the file being created, or give the script parameters that allow you to input the path at execution. Here is another StackOverflow article where a user had similar struggles, with two good solutions where one uses $args in the script and another uses the -InitializationScript parameter of Start-Job to set the $PWD: PowerShell Start-Job Working Directory
Often, Write-Host is low-priority as a selected output vs. using Write-Output / Write-Verbose / Write-Warning / Write-Error. More information about this can be found here: https://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/ - though, newer versions of PowerShell have added an information stream which I believe may make Write-Host output more accessible. More information on streams can be found with help about_Redirection

Invoking long NAnt process from PowerShell form Jenkins (using Pipelines)

I've been working on wrapping up the usage of some old NAnt scripts behind a Jenkins job. The Jenkins job itself is using the pipelines feature, a groovy DSL script, one of the steps is a PowerShell block, and it calls some a function that invokes NAnt, after working out lots of parameters to be parsed in.
I did have this working at some point just fine, but something has broken at some stage. The PowerShell function is called, and it triggers NAnt, and for the nearly an hour that it takes to complete, you get the output, as it happens, showing up in Jenkins.
This was done using something like Invoke-Expression "& $NAntExe $NAntFile $Target $ParameterString" | Write-Host, where $ParameterString is all the -D:Key=Value parameters.
I believe I had added the | Write-Host as without it, you only get the output at the very end, but we wanted to be able to see the progress as it's happening.
As I said, something has changed somewhere, and we were no longer getting any output from NAnt. I eventually found that removing the | Write-Host would restore the logs, but as I expected, we now have to wait for NAnt to finish before we see any logs.
What is the 'correct' way to invoke NAnt here to get the output as I desire? I want to see the output as it happens.
I've tried various ways of invoking NAnt, with no luck. Seems I'm having to settle for either "I get all the output in one go at the end" or "no output". I suspect this is not a PowerShell issue as such, but that's based on nothing but gut feeling.
Seems I can mostly recreate the symptoms I see in Jenkins. If I invoke NAnt through a fresh PowerShell session I get the same problem, I'm running something akin the following, which as far as I can tell would be the same as how the Jenkins plugin invokes PowerShell:
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy ByPass -Command 'Invoke-FunctionThatCallsNAnt'
Within my Invoke-FunctionThatCallsNAnt, I had initially, as I said above, just directly called NAnt and got no logging. I then update my function to pipe the output to Write-Host or I can remove the -NonInteractive flag and I will get the output from NAnt in real time. However, when I go to Jenkins, this does not resolve the problem, I end up with getting no output at all.
I'm not sure why it wouldn't stream. You should be able to write the command these ways:
& $NAntExe $NAntFile $Target $ParameterString
Or with whatever the nant command is.
$env:path += ';c:\program files\nant' # add to path if needed
nant.exe $NAntFile $Target $ParameterString
If it's not in the path, and the folder doesn't have spaces, you can put the whole path to it as well.
c:\nant\nant.exe $NAntFile $Target $ParameterString
EDIT:
Here's a way to run something in a path with spaces:
C:\Program` Files\Internet` Explorer\iexplore.exe
EDIT2:
It looks like you have to unblock the nant zip after downloading it: How do I resolve configuration errors with Nant 0.91?
Or unblock all the files after the fact:
get-childitem -recurse c:\nant-92 |
get-item -stream zone.identifier -erroraction silentlycontinue |
select -expand filename | get-item | unblock-file

How to generate comprehensive log file from PowerShell script

I am trying to write a migration/deployment script to deploy an application to an environment (DEV, QA, PROD) and I need to be able to fully log all output. This would include any status message I specifically put in the output stream (not a problem) as well as verbose output from all commands. For instance, if I'm calling Copy-Item, I want the full listing of each item copied.
I'm trying to figure out a way to do this throughout the entire script reliably. In other words, I don't want to rely on including -Verbose on every command (as it could be missed when someone else maintains the script in the future). I've been looking at things like $VerbosePreference as well as the possibility of calling my main cmdlet/function using -Verbose, with the hope being that either would apply to the entire script. But that appears to not be the case. While any Write-Verbose commands I use respect either approach, calls to Copy-Item only show the verbose listing if I specifically pass -Verbose to it. I'm really hoping I'm just missing something! Surely this is possible to do what I'm wanting!
Sample code:
function Main () {
[CmdletBinding()]
Param()
Begin {
Copy-Item C:\Temp\src\* -Destination C:\Temp\dest -Recurse -Force
Write-Output 'Main output'
Write-Verbose 'Main verbose'
Child
}
}
function Child () {
[CmdletBinding()]
Param()
Begin {
Copy-Item C:\Temp\src\* -Destination C:\Temp\dest -Recurse -Force
Write-Output 'Child output'
Write-Verbose 'Child verbose'
}
}
$VerbosePreference = 'SilentlyContinue'
Write-Output $VerbosePreference
Main
''
Main -Verbose
''
''
$VerbosePreference = 'Continue'
Write-Output $VerbosePreference
Main
''
Main -Verbose
Produces output:
SilentlyContinue
Main output
Child output
Main output
VERBOSE: Main verbose
Child output
VERBOSE: Child verbose
Continue
Main output
VERBOSE: Main verbose
Child output
VERBOSE: Child verbose
Main output
VERBOSE: Main verbose
Child output
VERBOSE: Child verbose
So, clearly $VerbosePreference and -Verbose are affecting the Write-Verbose, but that's about it. The Copy-Item is not displaying ANY output whatsoever (though it will if I specifically use -Verbose directly on that command).
Any thoughts? Am I going about this all wrong? Please help!
How about leveraging...
Tip: Create a Transcript of What You Do in Windows PowerShell
The PowerShell console includes a transcript feature to help you
record all your activities at the prompt. As of this writing, you
cannot use this feature in the PowerShell application. Commands you
use with transcripts include the following:
https://technet.microsoft.com/en-us/library/ff687007.aspx
... or the approaches provided / detailed here:
Enhanced Script Logging module (automatic console output captured to
file)
Automatically copy PowerShell console output to a log file (from
Output, Error, Warning, Verbose and Debug streams), while still
displaying the output at the console. Log file output is prepended
with date/time and an indicator of which stream originated the line
https://gallery.technet.microsoft.com/scriptcenter/Enhanced-Script-Logging-27615f85
Write-Log PowerShell Logging Function
The Write-Log PowerShell advanced function is designed to be a simple
logger function for other cmdlets, advanced functions, and scripts.
Often when running scripts one needs to keep a log of what happened
and when. The Write-Log accepts a string and a path to a log file and
ap
https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
* Update as per the OP comment*
See this discussion...
Powershell apply verbosity at a global level
where the -verbose flag is not supplied to the ni command. Is there a
way to set the Verbosity at a global PSSession level if I were to run
this script to force verbosity? The reason I ask is that I have a
group of about 60 scripts which are interdependent and none of these
supply -verbose to any commands they issue and I'd like to see the
entire output when I call the main entry point powershell script.
Powershell apply verbosity at a global level
Use PowerShell Default Parameter Values to Simplify Scripts
Changing default parameter values
When I was asked to write about my favorite Windows PowerShell 3.0
feature, my #1 $PSDefaultParameterValues came to mind immediately.
From my point of view, this was something I was looking for, for a
long time.
How does it work? With $PSDefaultParameterValues, you can define
(overwrite) default values of parameters for Windows PowerShell
cmdlets.
https://blogs.technet.microsoft.com/heyscriptingguy/2012/12/03/use-powershell-default-parameter-values-to-simplify-scripts/
See also:
Script Tracing and Logging
While Windows PowerShell already has the LogPipelineExecutionDetails
Group Policy setting to log the invocation of cmdlets, PowerShell’s
scripting language has plenty of features that you might want to log
and/or audit. The new Detailed Script Tracing feature lets you enable
detailed tracking and analysis of Windows PowerShell scripting use on
a system. After you enable detailed script tracing, Windows PowerShell
logs all script blocks to the ETW event log,
Microsoft-Windows-PowerShell/Operational. If a script block creates
another script block (for example, a script that calls the
Invoke-Expression cmdlet on a string), that resulting script block is
logged as well.
Logging of these events can be enabled through the Turn on PowerShell
Script Block Logging Group Policy setting (in Administrative Templates
-> Windows Components -> Windows PowerShell).
https://learn.microsoft.com/en-us/powershell/wmf/5.0/audit_script

Get-Content with wait parameter doesn't update in Powershell

I'm trying to monitor a file using Get-Content $path -wait in Windows Powershell V3.0. Sometimes when I execute this command line in Powershell it will function as expected. But sometimes it will only execute (or at least it seems like) get-content but without the -wait parameter. Even though the file get's updated it won't be shown in Powershell. If I cancel the command and rerun it it will show the updated file content.
What do I need to do?
EDIT: It seems to update blocks after a while. But it's not real-time really.
Not allowed to comment (don't have a 50 reputation), so have to give an Answer....
Less for Windows (http://gnuwin32.sourceforge.net/packages/less.htm) with the +F or Shift-F option (http://www.commandlinefu.com/commands/view/1024/make-less-behave-like-tail-f.) showed updated file content where PowerShell "get-content $path -wait" did not.