For example, in the sequence below $n is not updated unless I use
. Edit-History '$n +'
PS 10:42:50 199> $n = 5
PS 10:43:05 200> $n += 10
PS 10:43:05 201> Edit-History '$n +'
$n += 15
PS 10:43:12 203> $n
15
PS 10:43:15 204> h
Id CommandLine
-- -----------
198 function Edit-History ($entry) {...
199 $n = 5
200 $n += 10
201 Edit-History '$n +'
202 $n += 15
203 $n
-- Note: I use a TextBox so multi-line commands are easily edited.
######################################################################
#
# Edit-History {<history number>|<cmd prefix>}
#
# Creates a pop-up window to allow editing of the history item
# having ID <history Num> or the most recent command in history
# starting with <cmd prefix>. Once edited the new command is added
# to history and invoked.
#
######################################################################
function Edit-History ($entry) {
if ($entry -is [int]) {$entry = [int64]$entry}
if ($entry -isnot [int64]) {
$_Cnt=(Get-History -Count 1).id
$_Min = $_Cnt - $MaximumHistoryCount + 1
if ($_Min -lt 1) {$_Min = 1}
$ErrorActionPreference = 'SilentlyContinue'
WHILE ($_Cnt -ge $_Min -and
($_A = Get-History -id $_Cnt) -and
$_A -notlike ("$entry" + '*')) {$_Cnt--}
$ErrorActionPreference = 'stop'
if ($_A -and $_Cnt -ge $_Min) {$entry = $_Cnt}
}
if ($entry -is [int64]) {
$h = Get-History -id $entry|
Select-Object CommandLine,EndExecutionTime,ExecutionStatus,
Id,StartExecutionTime
[void][System.Reflection.Assembly]::LoadWithPartialName
("System.windows.forms")
$frmMain = new-object Windows.Forms.form
$frmMain.Size = new-object System.Drawing.Size @(500,300)
$frmMain.text = "Fix-History"
$TextBox = new-object System.Windows.Forms.TextBox
$frmMain.Controls.Add($TextBox)
$TextBox.Dock = [System.Windows.Forms.DockStyle]::Fill
$TextBox.MultiLine = $true
$TextBox.AcceptsReturn = $true
$TextBox.AcceptsTab = $true
$TextBox.lines = $h.CommandLine.Split("`n")
$FrmMain.Add_Shown({$FrmMain.Activate()})
[void]$FrmMain.showdialog()
$h.CommandLine = [string]::Join("`n",$TextBox.lines)
if ($h.CommandLine) {
Add-History $h
Write-host $h.CommandLine
Invoke-Expression $h.CommandLine
}
}
else {"No entry matches $([string]$entry + '*')"}
}
> PS 10:42:50 199> . .\eh.ps1
"RickB" wrote:
> I want to write a command that allows me to edit a previous command.
> All my attempts must be dotted to operate correctly.
> It seems that Invoke-History itself doesn't have this problem
> How do I avoid the need to dot my function?
>
> For example, in the sequence below $n is not updated unless I use
> .. Edit-History '$n +'
This is pretty tricky to do but you might be able to pull it off by
determining which variables in your current session state are different from
the variables in the caller's state and use Set-Variable with the -Scope
parameter to "pass" them back to the caller's scope. The reason that
Invoke-History cmdlet doesn't have this limitation is that compiled Cmdlets
do not get their own scope by default the way that functions and modules do.
The situation gets even worse if you implement your function in a module in
PowerShell v2, because modules, unlike ordinary functions, do not inherit
the scope of their caller and in fact, cannot access the scope of their
caller without reflection.
I had a similar problem when converting some of my functions to modules.
Several of my functions take ScriptBlocks that are designed to use the
standard "dollar underbar" variable for processing the current item. For
example:
get-process | max { $_.Handles }
This was a piece of cake in a normal .ps1 but when I implemented it as a
module, I realized that the $_ was being captured in the caller's scope and
any attempt I made in the Max function to set the $_ variable was ignored.
So I came up with the function below that can manipulate these variables
before executing the scriptblock. It's not exactly what you're trying to do
but maybe the reflection usage can point you in the right direction. The
simplest solution would be to just re-write your function as a compiled
cmdlet. You don't even need to mess with PSSnapIn registration anymore in
v2. Just derive from PSCmdlet, compile the DLL, then import it with
Import-Module.
Here's the function I made to post variables into a scriptblock's scope.
function Invoke-ScriptBlock {
[CmdletBinding()]
param (
[Parameter(Position=1, Mandatory=$true)]
[ScriptBlock]$ScriptBlock,
[Parameter(ValueFromPipeline=$true)]
[Object]$InputObject,
[Parameter()]
[Object]$ThisObject
)
begin {
# use reflection to get access to the session state
# that the scriptblock is attached to. this will allow us
# to reach "back" into a scope that is unavailable to
# this module in order to push a $_ variable into the
# session state in which the script block will execute
$SessionStateProperty =
[ScriptBlock].GetProperty('SessionState',([System.Reflection.BindingFlags]'NonPublic,Instance'))
$SessionState = $SessionStateProperty.GetValue($ScriptBlock, $null)
}
process {
# set the underbar value before calling the scriptblock
# note that the context in which the scriptblock was defined
# may already have a current underbar value so we need to
# store the old one and set it back when we're done
$OldUnderBar = $SessionState.PSVariable.GetValue('_')
$OldThis = $SessionState.PSVariable.GetValue('this')
try {
if ($InputObject -ne $null) { $SessionState.PSVariable.Set('_',
$InputObject) }
if ($ThisObject -ne $null) {
$SessionState.PSVariable.Set('this', $ThisObject) }
$SessionState.InvokeCommand.InvokeScript($SessionState,
$ScriptBlock, @())
}
finally {
$SessionState.PSVariable.Set('_', $OldUnderBar)
$SessionState.PSVariable.Set('this', $OldThis)
}
}
}
"RickB" <rbie...@i1.net> wrote in message
news:eb41f0c9-b54e-40da...@b16g2000yqb.googlegroups.com...
On Mar 17, 12:03 pm, Bob Landau <BobLan...@discussions.microsoft.com>
wrote:
> > }- Hide quoted text -
>
> - Show quoted text -
at the command line I did this
. .\eh.ps1 # this is dot-sourcing and must be done for Edit-History to be
visible in the shell
from here I'm able to use your function Edit-History using either
edit-history <id>
or
edit-history <cmd>
your textbox pops up and I whether I change the command-line or not once its
dismissed the textbox (clicking on the 'X' or F4 ) whatever command line that
was in the textbox is now on the command line and it gets invoked.
I'm not seeing the problem
"RickB" wrote:
> By dotted I mean
> .. Edit-History '$n +'
I know what you mean about messing with the scope.
(get-variable StartIndex -scope 1).value++
But it would be difficult indeed to do that generically.
The problems with compiled modules is they can't be executed remotely.
They must be installed first. That may not seem like a problem for
this type of command but in a roundabout way it is. I can't justify
installing personal conveniences like this on a production
system. .ps1 files must be signed too so I don't even have an
editable profile.
This doesn't keep me from having personal conveniences though. I can
paste whatever I want into a powershell session.
When you are issuing large ad hoc statements in this manner there is
no immediately simple way to edit commands like function
definitions.
This is the problem from which my effort here springs.
It sounds like you are fairly convinced this can't be done simply
either.
When faced with declaring everything $global:A or putting a dot I
think I'll settle for the dot.
Yes, the command executes as expected.
The problem is that if the command involves results they are discarded
unless you dot the command itself.
. edit-history <cmd>
Try my original example.
$a = 2
$a += 3
edit-history '$a'
At this point, $a should contain 8 but it only contains 5.
. edit-history '$a +'
now it does contain 8.
Unless I dot the edit-history command the assignment happens against a
variable that is local to edit-history and so that copy disappears
when edit-history ends. The global version is unchanged.
Simply using
invoke-history does not have this problem.
That is, you are not required to type
. invoke-history ...
for the command to be executed at the global scope.
Josh
"RickB" <rbie...@i1.net> wrote in message
news:1d82802b-0baf-455b...@a39g2000yqc.googlegroups.com...
Your original post asked about how a function can execute a scriptblock in
the scope of its caller so that changes to variables in that scriptblock are
"seen" by the caller. And unfortunately you can't as far as I can tell from
spelunking with reflector.
What I said (and whether or not it's useful to your problem is up to you) is
that a compiled cmdlet *can* do this because a compiled cmdlet does not
implicitly get a private scope. A compiled cmdlet can also be xcopy deployed
as easily as a .ps1 file. Again, maybe your situation rules out the use of
compiled cmdlets but it's a fact that I chose to include.
Josh
"RickB" <rbie...@i1.net> wrote in message
news:036ee5b7-deda-40dc...@e38g2000yqa.googlegroups.com...
I don't see the 'prevent tampering' view as entirely accurate.
AllSigned prevents running any script that has not been signed by
a trusted authority. Were I to add a new .ps1 file (not tamper
with an existing one) it would not run unless properly signed.
If a system can be configured such that Import-Module cannot load
a dll that isn't signed by a trusted authority then I see no danger
(unless a users doesn't avail themselves of that protection...).
If I can't prevent Import-Module from loading an unsigned dll
then security seems seriously compromised on any system where
Import-Module is invoked, basically for the same reasons that
not requiring .ps1 files to be signed introduces a risk.
An EXE won't change the behavior of existing signed scripts.
For example, to intercept and publish a password someone enters
to authorize the execution of some further process or function.
On Mar 25, 4:51 pm, "Josh Einstein" <josheinst...@hotmail.com> wrote:
> I have no idea how AllSigned affects compiled modules, but no you're no more
> at risk than running an .exe file. AllSigned doesn't protect you from
> anything except PowerShell script and even then all it does is ensures that
> the signed script cannot be modified after signing. .NET already provides a
> signing mechanism that you could use to ensure a strong named DLL hasn't
> been tampered with but that's besides the point.
>
> Your original post asked about how a function can execute a scriptblock in
> the scope of its caller so that changes to variables in that scriptblock are
> "seen" by the caller. And unfortunately you can't as far as I can tell from
> spelunking with reflector.
>
> What I said (and whether or not it's useful to your problem is up to you) is
> that a compiled cmdlet *can* do this because a compiled cmdlet does not
> implicitly get a private scope. A compiled cmdlet can also be xcopy deployed
> as easily as a .ps1 file. Again, maybe your situation rules out the use of
> compiled cmdlets but it's a fact that I chose to include.
>
> Josh
>
> "RickB" <rbiel...@i1.net> wrote in message
>
> news:036ee5b7-deda-40dc...@e38g2000yqa.googlegroups.com...
>
>
>
> > Either what you are saying isn't useful to my problem or it appears to
> > be a security risk.
> > It sounds like you are saying that anyone who issues the Import-Module
> > statement is at risk of importing virtually anything. Unlike
> > executing a .ps1 file where AllSigned can protect you there is no
> > simple way to know exactly what a binary file will do.
> > If the module must be signed then there is no security risk but I'm
> > back to where I started.
> > The whole purpose of the function was to allow me to use and edit
> > functions on a system where AllSigned is in place and I don't have
> > signing authority.
>
> > Josh Einstein wrote:
> >> For the record, with PowerShell v2, compiled cmdlets are as portable as
> >> .ps1
> >> files. In fact, my compiled cmdlet project which used to be deployed as a
> >> PSSnapIn is now loaded in my profile with Import-Module - no registration
> >> needed.
>
> >> Josh
>
> >> > I know what you mean about messing with the scope.- Hide quoted text -
>
> - Show quoted text -...
>
> read more »
As for Import-Module and compiled cmdlets... in .NET you can sign DLL's or
EXE's with a .snk file very easily. There is no need to trust publishers,
but by signing it, you can ensure that when you load an assembly using its
strong name, an imposter DLL can't be put in its place because it would have
a different public key and thus wouldn't match the identity of the assembly
you attempted to load.
"RickB" <rbie...@i1.net> wrote in message
news:dbfe34f7-920f-4beb...@r33g2000yqn.googlegroups.com...