Powershell execution policy remotesigned contradiction? - powershell

please look at the following URL: URL
Now it says the following about downloaded scripts:
"Runs scripts that are downloaded from the Internet and not signed, if the scripts are unblocked, such as by using the Unblock-File cmdlet."
I just downloaded a script from the technet gallery (PS2EXE) and I could run the test script that was included just fine without using the Unblock_file cmdlet. What is going on? Am i misunderstanding what Microsoft is telling me or is this a glitch?

help unblock-file:
Internally, the Unblock-File cmdlet removes the Zone.Identifier alternate data stream, which has a value of "3" to indicate that it was downloaded from the Internet.
The idea of a file being "remote" or "coming from the internet" is data on your local computer filesystem which has to be put there by the tool that downloads the file, it's not included in the file during the download.
If you downloaded a file through Internet Explorer, maybe FireFox, Invoke-WebRequest, these will add it. If you download with something else, the tool might not add this alternate stream.
See how it behaves:
# Show folder is empty
PS C:\temp\> Get-ChildItem
# Make a test script which prints Hello World, and run it
PS C:\temp\> "'Hello World'" | Set-Content -Path .\test.ps1
PS C:\temp\> .\test.ps1
Hello World
# Show the file exists
PS C:\temp\> Get-ChildItem
Directory: C:\temp\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 01/08/2018 22:07 15 test.ps1
# Add the Zone Identifier alternate data stream
PS C:\temp\> "[ZoneTransfer]`nZoneId=3" | Set-Content -Path 'test.ps1' -Stream 'Zone.Identifier'
# Show that it doesn't appear in a normal directory listing:
PS C:\temp\> Get-ChildItem
Directory: C:\temp\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 01/08/2018 22:08 15 test.ps1
# Show how it blocks the file from running
PS C:\temp\> .\test.ps1
.\test.ps1 : File C:\temp\test.ps1 cannot be loaded. The file C:\temp\test.ps1 is not digitally signed. You cannot
run this script on the current system. For more information about running scripts and setting execution policy, see
about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ .\test.ps1
+ ~~~~~~~~~~
+ CategoryInfo : SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
# Show file content
PS C:\temp\> Get-Content -Path .\test.ps1
'Hello World'
# Show alternate data stream content
PS C:\temp\> Get-Content -Path .\test.ps1 -Stream 'Zone.Identifier'
[ZoneTransfer]
ZoneId=3
# Unblock-File removes this alternate stream
PS C:\temp\> Unblock-File .\test.ps1
# Script runs again
PS C:\temp\> .\test.ps1
Hello World
So the main question is, if you run Get-Content file.ps1:Zone.Identifier and see the ZoneId is 3 and can still run the script, and Get-ExecutionPolicy is RemoteSigned, then you have something odd going on.
But my guess is the download tool did not add this data, so the file looks just like a locally created one.
NB. RemoteSigned is not intended to be a security feature, it's intended to be a "help guard against accidentally running scripts before reading them and deliberately choosing to run them" check, like an "are you sure?" box, not like a password prompt.

Related

PowerShell asks "How do you want to open this file" when executing command

After upgrading from to Windows 11 the Select-String cmdlet stopped working and everytime I use it, it tries to "open an unknown file" as in image bellow:
I found it out when I tried to run one of my ps scripts from batch file - in Win 10 it worked well, but in Win 11 it doesn't. When I run the code in VSCode, it works correctly, I have no clue why.
I tried to copy texts from VSCode (in case of different encoding) to a new file and directly to PowerShell terminal, but none of these worked.
I tried also the new Windows 11 terminal and updating PowerShell to the latest version (7.2.2 x64), but nothing worked.
Dis/Enabling the system function of Windows PowerShell 2.0 has no effect in this case as well.
EDIT:
I found out that if I run the command Update-Script (without any parameters) at the beginning of the script, Select-String works as expected (before that the cmdlet seems to not exist at all - Get-Help Select-String didn't find anything, but after using Update-Script it finds appropriate help). Some other commands does the same (eg. Trace-Command). See the output:
PS C:\> Get-Command Select-String
CommandType Name Version Source
----------- ---- ------- ------
Application Select-String 0.0.0.0 C:\WINDOWS\system32\Select-String
PS C:\> Update-Script
PS C:\> Get-Command Select-String
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Select-String 3.1.0.0 Microsoft.PowerShell.Utility
The Trace-Command "makes" the Select-String works properly as the Update-Script does and gives this output:
DEBUG: CommandDiscovery Information: 0 : Looking up command: Select-String
DEBUG: CommandDiscovery Information: 0 : Cmdlet found: Select-String Microsoft.PowerShell.Commands.SelectStringCommand
hello everybody!
Why do I have to do that (call the Update-Script), when Select-String should work without it?
Well, there are a lot of discussions (not really solved) about powershell "how do you want to open this file", but finally, this was the problem of mine:
https://serverfault.com/questions/1038546/powershell-start-job-returns-a-how-do-you-want-to-open-this-file-with-some-cm
Solution:
Delete file c:\Windows\System32\Select-string (0 bytes, no file extension). I have no idea how it got there...

How to change the current directory using .bat file in Windows PowerShell?

I am learning Windows PowerShell and I am struggling with the very basic task, how to create a .bat file to change the current directory? The simple .bat file with cd mydir inside worked well using cmd.exe, but it does not work in PowerShell:
PS C:\Users\ET\test> dir
Directory: C:\Users\ET\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 01/10/2021 10:57 mydir
-a---- 01/10/2021 10:58 10 changeDir.bat
PS C:\Users\ET\test> type changeDir.bat
cd mydir
PS C:\Users\ET\test> .\changeDir.bat
C:\Users\ET\test>cd mydir
PS C:\Users\ET\test>
You see that my current directory has not changed after executing the .bat file.
Works as expected using cmd.exe:
C:\Users\ET\test>changeDir
C:\Users\ET\test>cd mydir
C:\Users\ET\test\mydir>
Because a batch file (.bat, .cmd) runs in a child process (via cmd.exe), you fundamentally cannot change PowerShell's current directory with it.
This applies to all calls that run in a child process, i.e. to all external-program calls and calls to scripts interpreted by a scripting engine other than PowerShell itself.
While the child process' working directory is changed, this has no effect on the caller (parent process), and there is no built-in mechanism that would allow a given process to change its parent's working directory (which would be a treacherous feature).
The next best thing is to make your .bat file echo (output) the path of the desired working directory and pass the result to PowerShell's Set-Location cmdlet.
# Assuming that `.\changeDir.bat` now *echoes* the path of the desired dir.
Set-Location -LiteralPath (.\changeDir.bat)
A simplified example that simulates output from a batch file via a cmd /c call:
Set-Location -LiteralPath (cmd /c 'echo %TEMP%')
If you're looking for a short convenience command that navigates to a given directory, do not use a batch file - use a PowerShell script or function instead; e.g.:
function myDir { Set-Location -LiteralPath C:\Users\ET\test\myDir }
Executing myDir then navigates to the specified directory.
You can add this function to your $PROFILE file, so as to automatically make it available in future sessions too.
You can open $PROFILE in your text editor or add the function programmatically, as follows, which ensures on-demand creation of the file and its parent directory:
# Make sure the $PROFILE file exists.
If (-not (Test-Path $PROFILE)) { $null = New-Item -Force $PROFILE }
# Append the function definition to it.
#'
function myDir { Set-Location -LiteralPath C:\Users\ET\test\myDir }
'# | Add-Content $PROFILE
try the following
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Write-host "running in directory $dir"
Actually, I found a way to start PowerShell in the directory I need. For that, I am using Windows Terminal app https://www.microsoft.com/en-gb/p/windows-terminal/9n0dx20hk701. I've configured the settings.json file this way:
"guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"name": "PowerShell",
"source": "Windows.Terminal.PowershellCore",
"startingDirectory": "C:\\Haskell\\HaskellWikiBook"

Windows Defender and handling downloaded PowerShell scripts

I use Set-ExecutionPolicy RemoteSigned and download some scripts and modules. These are my scripts so they are not signed. I could use Bypass or RemoteSigned or Unrestricted. I feel that Unrestricted feels a bit over the top so I got with RemoteSigned and indeed, even though my scripts are not signed, I can download them and run them... for a while. Then, "Windows Defender" catches up and completely deletes my scripts. My questions are:
After I download a script, is there a programmatic way with PowerShell to instruct Windows Defender to mark that script on the exclusion list?
Would you say that Unrestricted is a bit unsafe? If so, what is the process of making these scripts signed (or self-signed?), or is this not possible? i.e. Set to Unrestricted so that files are not nuked, then download the file, then somehow put it on an exclusion list, then set the ExecutionPolicy back to RemoteSigned?
Downloaded files are marked as from the internet. Your need to unblock them. Use the built-in cmdlet for that.
# Get specifics for a module, cmdlet, or function
(Get-Command -Name Unblock-File).Parameters
(Get-Command -Name Unblock-File).Parameters.Keys
Get-help -Name Unblock-File -Examples
# Results
<#
Unblock-File -Path C:\Users\User01\Documents\Downloads\PowerShellTips.chm
dir C:\Downloads\*PowerShell* | Unblock-File
Get-Item * -Stream "Zone.Identifier" -ErrorAction SilentlyContinue
C:\ps-test\Start-ActivityTracker.ps1
Get-Item C:\ps-test\Start-ActivityTracker.ps1 | Unblock-File
#>
Get-help -Name Unblock-File -Full
Get-help -Name Unblock-File -Online
Any script you use will be looked at for actions it is performing. Your AV solution (Windows Defender notwithstanding) will take action(s) on it if it appears to be doing unexpected/nefarious things at any point. This has nothing to do with whether they are signed or not, or what ExecutionPolicy you set.
EP = only means allow a script(s) to run, not control what the script does/is going to do and the EP is not a security boundary, as documented in the help files.
Unblock-File Module: Microsoft.PowerShell.Utility
Unblocks files that were downloaded from the Internet.
This is all related to Windows ADS.
'windows alternate data streams downloaded file'
### Detecting Alternate Data Streams with PowerShell and DOS
dir /s /r | find ":DATA"
Get-Item –Path 'C:\users\me\desktop\*' -Stream *
Get-Content –Path 'C:\users\me\some_file.exe' -Stream zone.identifier
# Results
<#
[ZoneTransfer]
ZoneId=3
1
2
#>
Downloaded file via zone 3, we now know that is the Internet Zone as depicted in the chart below.
Value Setting
0 My Computer
1 Local Intranet Zone
2 Trusted sites Zone
3 Internet Zone
4 Restricted Sites Zone
Or using MS SysInternals: streams.exe

How to pause a powershell script until a debugger attaches

I have an application which allows you to run a user provided PowerShell script. I want inject some code at the top of the script to pause the running script on the first line, and wait for a debugger (ie, PowerShell ISE) to attach.
A good example of what I want to achieve is how DSC pauses and waits for you to attach:
PS C:\> Enable-DscDebug -BreakAll
PS C:\> Start-DscConfiguration .\temp\DSCTestClass -wait -force
WARNING: [DEV-14257-44]: [DSCEngine] Warning LCM is in Debug 'ResourceScriptBreakAll' mode. Resource script processing will be stopped to wait for PowerShe1l script debugger to attach.
WARNING: [DEV-14257-44]: [[FileResource]file] Resource is waiting for PowerShell script debugger to attach. Use the following commands to begin debugging this resource script:
Enter-PSSession -ComputerName DEV-14257-44 -Credential <credentials>
Enter-PSHostProcess -Id 596 -AppDomainName DscPsPluginWkr_AppDomain
Debug-Runspace -Id 4
(this example from https://blogs.msdn.microsoft.com/powershell/2016/03/14/debugging-powershell-dsc-class-resources/)
Unfortunately, when I try and setup a similar kind of wait, powershell.exe automatically launches the command line debugger:
Given the file test.ps1:
set-psbreakpoint -script "test.ps1" -line 4
write-host "pid is $pid"
write-host "Pausing for debugger to attach"
write-host "Paused waiting for debugger to attach"
When I run it (even in -noninteractive mode), it launches the command line debugger:
PS C:\temp\PowershellDebugging> powershell -file .\test.ps1 -noninteractive
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 test.ps1 4
pid is 13228
Pausing for debugger to attach
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\temp\PowershellDebugging\test.ps1:6'
At C:\temp\PowershellDebugging\test.ps1:6 char:1
+ write-host "Paused waiting for debugger to attach"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\temp\PowershellDebugging>
How can I get PowerShell to break/pause execution without launching the command line debugger?
EDIT:
The script that is being executed only exists for a short time (it is written to disk, executed, then cleaned up). Also, the powershell.exe instance that runs it only exists for a short time - there is no long running process to manually attach to and set breakpoints on before execution.
After getting some help from the PowerShell team (thanks!), it turns out the key is using New-RunSpace. Normally, if you just execute a script, it will run in the default RunSpace, and that will have the built-in PowerShell debugger already attached.
The following demonstrates how to use it:
# create a fake script that simulates the user provided scrip
#'
Write-Output "Starting Test"
1..20 | foreach {
Write-Output "I Love PowerShell!"
}
Write-Output "Test Complete"
'# | Set-Content -Path c:\temp\Test.ps1
# create a launcher script
#'
Write-Output "Process Id: $pid"
$ps = [powershell]::Create([System.Management.Automation.RunspaceMode]::NewRunspace)
Write-Output "Runspace Id: $($ps.Runspace.Id)"
$ps.AddScript(‘Wait-Debugger; C:\temp\Test.ps1’).Invoke()
'# | Set-Content -Path c:\temp\LaunchTestInNewRunspace.ps1
# run the launch script
PS C:\> powershell -File c:\temp\LaunchTestInNewRunspace.ps1
Process Id: 14288
Runspace Id: 2
In another window:
# in another window
PS C:\> Enter-PSHostProcess -Id 14288
[Process:14288]: PS C:\> Debug-RunSpace -Id 2
Debugging Runspace: Runspace2
To end the debugging session type the 'Detach' command at the debugger
prompt, or type 'Ctrl+C' otherwise.
Entering debug mode. Use h or ? for help.
At line:1 char:16
+ Wait-Debugger; C:\temp\Test.ps1
+ ~~~~~~~~~~~~~~~~
This breaks into the debugger, ready to step into Test.ps1.

Where to put PowerShell profile scripts if standard locations are forbidden?

In our corporate environment, I am having difficulty with creating a PowerShell profile scripts.
To prevent users from writing documents on the local disk, the "Documents" directory is forced to be on a network drive. Commonly the "H:" (home) drive.
Likewise, users are forbidden from writing under C:\Windows\System32.
Where can I put the ISE profile script if these two are not available?
PSVersion 5.0.10586.117
PS C:\Windows\System32\WindowsPowerShell\v1.0> $HOME, $PSHOME
C:\Users\pwatson
C:\Windows\System32\WindowsPowerShell\v1.0
See also: Help-About about_Profiles
When I am not connected to the network, these are the $profile settings. I still cannot write under C:\Windows\System32 and the CurrentUser values are invalid.
PS C:\Windows\System32\WindowsPowerShell\v1.0> $profile | Get-Member -Type NoteProperty | ForEach-Object {$_.ToString
()}
string AllUsersAllHosts=C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
string AllUsersCurrentHost=C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
string CurrentUserAllHosts=WindowsPowerShell\profile.ps1
string CurrentUserCurrentHost=WindowsPowerShell\Microsoft.PowerShell_profile.ps1
PS C:\Windows\System32\WindowsPowerShell\v1.0>
One option is to create a shortcut with a target like this:
%systemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoExit -file C:\somewhere\myprofile.ps1
and then always use this shortcut to start PowerShell.
This is not actually using the built-in PowerShell profile concept but it is dot-sourcing a ps1 file that behaves pretty much like a profile file.
If you like to start PowerShell from cmd.exe, create a batch-file with the same content as above and put it somewhere in your path (if you have permissions to do so)