Persist current directory between PowerShell sessions - powershell

Is there a way to make PowerShell Console remember the current directory from the last time it was run and use that as the default location for the next instance?
eg.
Open PowerShell Console
Set-Location c:\tmp
Close PowerShell
Open PowerShell Console
Get-Location returns c:\tmp

You could change the prompt function to save location changes to a user environment variable:
$Function:prompt = {
if([System.Environment]::GetEnvironmentVariable("LocationMemory",[System.EnvironmentVariableTarget]::User) -ne $PWD){
[System.Environment]::SetEnvironmentVariable("LocationMemory",$PWD,[System.EnvironmentVariableTarget]::User)
}
"PS $($ExecutionContext.SessionState.Path.CurrentLocation)$('>' * ($NestedPromptLevel + 1))"
}
And then check if the environment variable exists and is valid when powershell starts
if(($LastLocation = [System.Environment]::GetEnvironmentVariable("LocationMemory",[System.EnvironmentVariableTarget]::User))){
if((Test-Path $LastLocation -PathType Container)){
Set-Location $LastLocation
}
}
Put both snippets in your profile to make it work. You probably want to limit it to $Profile.CurrentUserCurrentHost

I don't know if this is perfect but testing on my machine it seems to work. First check out this question/answer, this is the icing of the cake. You are basically going to have to log the current path of your session through an exiting event with PowerShell.
You can add this bit of code to your $PROFILE so it will always register the exit event and then set the path.
Register-EngineEvent PowerShell.Exiting -Action {(Get-Location).Path | Out-File 'C:\Users\wshaw\Documents\WindowsPowerShell\LastPath.txt' -Force}
$lastPath = 'C:\Users\wshaw\Documents\WindowsPowerShell\LastPath.txt'
if (Test-Path $lastPath) {
Set-Location (Get-Content $lastPath)
}

Thanks to #Shawn and #Mathias for both giving great answers. I ended up combining your approaches to come up with the following:
Register-EngineEvent PowerShell.Exiting -Action {
Write-Warning "Saving current location"
[System.Environment]::SetEnvironmentVariable("LocationMemory", (Get-Location).Path, [System.EnvironmentVariableTarget]::User)
} | Out-Null
$lastPath = [System.Environment]::GetEnvironmentVariable("LocationMemory",[System.EnvironmentVariableTarget]::User)
if (($lastPath -ne $null) -and (Test-Path $lastPath)) {
Set-Location $lastPath
}
There is around a 4 second delay for me when SetEnvironmentVariable is called, which is why I added the Write-Warning (otherwise you might think that the console window isn't closing after you exited or clicked on the Close Window control)

Related

Event for when PowerShell finishes a command?

I am looking to create a InputObject for The Register-ObjectEvent cmdlet.
The object needs to hold the event for when a PowerShell command finishes.
So if I type dir into PowerShell it gets registered with the Register-ObjectEvent.
I am having trouble finding the event for when powershell finishes a command, or if there is even one?
My code at the moment is just:
PS C:\>$CommandFinishWatcher = /* Watches for a finished powershell command. */
PS C:\>register-objectEvent -InputObject $CommandFinishWatcher -EventName "PowerShellCommandFinished"
You can use the automatic variable $? to check the status of the most recent command that powershell ran. It contains the execution status of the last operation.
https://ss64.com/ps/syntax-automatic-variables.html
Get-Content -path C:\Test
if($? = "FALSE")
{Write-Host "The get-content command failed."}
if($? = "TRUE")
{Write-Host "The get-content command succeeded."}

How to check if a folder has no files locked by any process?

I am trying to write a PowerShell script that will check if none of the files in a particular folder is locked by any process; if true, only then delete the folder.
One way is to check locks on each file by opening them in RW mode iteratively - see if exception occurs. But that's too cumbersome.
Is there a way to check the same thing for a folder? I tried to use Remove-Item with -WhatIf flag but it's of no use because the command doesn't return any value - nor did it detect the locked files. If I try to run Remove-Item without the flag to look for exception, then it removes the free files only whereas I want to have a All or None condition.
As long as you're not going to distribute the solution, you could use handle.exe. It shows all files/folders in use and who is using it.
$path = "C:\Users\frode\Desktop\Test folder"
(.\handle64.exe /accepteula $path /nobanner) -match 'File'
explorer.exe pid: 1888 type: File F3C: C:\Users\frode\Desktop\Test folder
explorer.exe pid: 1888 type: File 2E54: C:\Users\frode\Desktop\Test folder
notepad.exe pid: 2788 type: File 38: C:\Users\frode\Desktop\Test folder
WINWORD.EXE pid: 2780 type: File 40: C:\Users\frode\Desktop\Test folder
WINWORD.EXE pid: 2780 type: File 6FC: C:\Users\frode\Desktop\Test folder\New Microsoft Word-dokument.docx
If you only want a yes/no answer you can use an if-test and -match to see it if returns any results.
if((.\handle64.exe /accepteula $path /nobanner) -match 'File') { "IN USE, ABORT MISSION!" }
Usually you're able to delete a folder even if explorer.exe is browsing inside it, so you could usually exclude that process from the results:
if((.\handle64.exe /accepteula $path /nobanner) -match 'File' -notmatch 'explorer.exe') { "IN USE, ABORT MISSION!" }
If you don't wanna install handle, you can look wich process is running in the given directory or subdirectory.
$lockedFolder="C:\Windows\System32"
Get-Process | % {
$processVar = $_
$_.Modules | %{
if($_.FileName -like "$lockedFolder*"){
$processVar.Name + " PID:" + $processVar.id + " FullName: " + $_.FileName
}
}
}
This is not a complete solution, but is a starting point, on which you can build.
I have a txt file called "demo.txt" under: "E:\Work\Powershell\scripts\demo". And I am using Notepad.exe to open it. In this case, I will not be able to delete the folder "demo", until I close the notepad.
You will have to query the "Win32_process" class to understand, if the notepad is using the file.
Get-WmiObject win32_process | where {$_.name -eq 'notepad.exe'}
In the output of the above cmdlet, the "CommandLine" property, will show you what files are currently being used by that process.
You may have to iterate over this to have a complete solution for your problem.
Certain processes like - "chrome.exe" will have a huge listing under "CommandLine" property, Since chrome or iExplorer will not prevent you to delete the folder when they have the file opened, you may ignore those kind of processes.
Note: The "FileName" under "(Get-Process).Modules", provides you only the PATH where you can find "notepad.exe" (i.e., E:\Work\Powershell\scripts\demo )
Or you could just attempt to rename-item on the $path and if it fails it has a lock on it. Throw a little try{ catch{ in there and you get something maybe like below. Nice and Simple
#check for locking handle
Function Check-LockedItem {
param([string]$path)
$name = Split-Path "$path" -leaf
$pathorg = Split-Path "$path" -Parent
try {Rename-Item -Path "$path" -NewName "$name--" -ErrorAction Stop}
catch {write-host "A process is locking $path. Please close the process and try again"}
Finally {Rename-Item -path "$pathorg\$name--" -NewName $name -ErrorAction SilentlyContinue}
}
Usage
Check-LockedItem -Path "C:\Mypathtotest"

I am trying to debug my PowerShell but I cannot get anything to output

I have this script here:
Write-Host "Checking files"
#to make it more dynamical you can save on one file
#all the file names including extension in different lines.
#For example on path C:\FilesToWatch\watcher.txt
#$filestowatch=get-content C:\FilesToWatch\watcher.txt
#$filestowatch="felicio.txt","marcos.txt"
$userFiles=dir C:\G\user\less\
$adminfiles=dir C:\G\admin\less\
#Optionally instead of use this if approach you can
#$adminfiles=dir C:\GT\admin\src\admin\wwwroot\content\less|? {$filestowatch -contains $_.Name}
#$userFiles=dir C:\GT\user-staging\src\user-staging\wwwroot\content\less|? {$filestowatch -contains $_.Name}
#loading in the above manner the first if statement on code bellow can be removed because
#We make sure that $userFiles and $adminfiles only have correct file to monitor
foreach($userfile in $userFiles)
{
if($filestowatch -contains $userfile.Name)
{
$exactadminfile= $adminfiles | ? {$_.Name -eq $userfile.Name} |Select -First 1
#my suggestion is to validate if it got the file.
#By now because of my lazy i will not call the test-path to validate if it got the file
#I'm assuming all directory are exact copy of each other so it will find the file.
if($exactadminfile.LastWriteTime -gt $userfile.LastWriteTime)
{
Write-Verbose "Copying $exactadminfile.FullName to $userfile.FullName "
Copy-Item -Path $exactadminfile.FullName -Destination $userfile.FullName -Force
}
else
{
Write-Verbose "Copying $userfile.FullName to $exactadminfile.FullName "
Copy-Item -Path $userfile.FullName -Destination $exactadminfile.FullName -Force
}
}
}
I understand there may be a problem with the script but when it runs I do not even see the output from the first line. Can anyone give me ideas on how can I debug this?
Here's how I run (or try to run the script):
PS C:\GT> .\watcher.ps1
When I tried a script with just the first line only it worked okay. Is there a way that I can run this script in debug mode so it would give me an idea what could be wrong?
You should open this script up in the ISE so you can use the debugger. Select a line that you want to set a breakpoint at and hit F9 to set it. Go ahead and run the script and once it gets to that line, it will break into the debugger. From there you can start to inspect your variables and step through the code to hopefully locate the issue.
Some ISE Debugger Shortcuts:
F5 - Run/Continue
F11 - Step Into
F10 - Step Over
F11 - Step Out
CTRL+SHIFT+D - Display Call Stack
CTRL+SHIFT+L Display Breakpoints
F9 - Toggle Breakpoint
CTRL+SHIFT+F9 - Remove all breakpoints
SHIFT+F5 - Stop Debugger
More info on debugging can be found in the help files.
Get-Help about_debuggers

Powershell script changes directory of parent powershell

When running a powershell script which changes a directory via cd (or set-location/push-location/etc.), the console that the script was run from also ends up in that directory.
Example:
script.ps1
cd c:\tmp
# do stuff
If I run this from c:\users\me, i end up in c:\tmp.
PS C:\users\me> .\script.ps1
PS C:\tmp> |
Now I know that I could use push-location and later do pop-location. However this wouldn't work if the script stopped somewhere in the middle (via Exit).
How can I solve this? Why doesn't the script have it's own location stack?
You could always use Try/Catch/Finally. Set the current directory path to a variable and then cd c:\tmp before the Try, and have the directory changed to variable in the Finally?
Example 1
$path = (Get-Item -Path ".\" -Verbose).FullName
cd c:\temp
Try
{
#Do stuff
#exit is fine to use
#this will output the directory it is in, just to show you it works
Write-Host (Get-Item -Path ".\" -Verbose).FullName
}
Catch [system.exception]
{
#error logging
}
Finally
{
cd $path
}
Example 2 using popd and pushd
pushd c:\temp
Try
{
#Do stuff
#exit is fine to use
#this will output the directory it is in, just to show you it works
Write-Host (Get-Item -Path ".\" -Verbose).FullName
}
Catch [system.exception]
{
#error logging
}
Finally
{
popd
}
I'd also recommend looking at what arco444 suggested, which is calling the powershell script via the -File parameter. Depending on the scenario that might work as an option.

PowerShell Test-Path returns False when testing a network share

The user has appropriate permissions to read the share, and the share maps properly.
This issue seems to happen only in PowerShell v2.0.
If I remove all mapped drives, remap one drive, and run Test-Path:
net use /delete * /y
net use G: \\somefileserver\share
Test-Path G:\
Test-Path returns False, even though the drive is clearly mapped, and I can access it through Windows Explorer.
If I close the PowerShell session, and open a new session, Test-Path returns True, as it should. Any ideas on, (A): what could be causing this, and how do I make it work properly? or (B): Is there another way to test for the existence of a network drive besides Test-Path? I am attempting to write a logon script to map user's network drives and this behavior is driving me insane.
I do think I found the answer with this:
Test-Path $('filesystem::\\Server\share$\install.wim')
If the Test-Path cmdlet is truly buggy, then I'd suggest using the Exists() method on the System.IO.Directory .NET class.
[System.IO.Directory]::Exists('G:\'); # This returns $true for me
To validate a UNC, use:
[bool]([System.Uri]$path).IsUnc
I had the exact same problem trying two network paths like this:
$verif_X = Test-Path "X:\"
$verif_S = Test-Path "S:\"
if($verif_X -and $verif_S){
Write-Host "X: and S: network paths found"
}
else{
Write-Host "Not all network paths found"
}
... At first, I always got > "Not all network paths found" which seemed really strange to me...
Then I tried to put those paths in simple quotes (') and searched for an existing folder inside those both network paths like this:
$verif_X = Test-Path 'X:\ISOs'
$verif_S = Test-Path 'S:\Scripts'
And then
if($verif_X -and $verif_S){[..]}
condition is accepted..
I think the Test-path cmdlet is a bit buggy as we enter only the first letter of a network path... but like this I do not get errors any more...
I found also this answer which can be found here:
It says that one can use the .NET framework to do so:
$path = "C:\text.txt"
if([System.IO.File]::Exists($path)){
# file with path $path exists
}
Works fine if its a file. To find a folder, use:
$path = x:\
if([System.IO.Directory]::Exists($path)){
# file with path $path exists
}
For me the only way to fix it was to use the full UNC, rather than the share name.
So instead of \\server\share I used \\server\c$\sharedir.
Another issue I ran into was when using Import-Module sqlps, I had to make sure to CD back into the file system and then the PowerShell commands worked normally.
SHORT ANSWER
if(-Not(Test-Path "filesystem::\\$server\c$")) {Write-Error "Server not found: $server"; continue}
If Test-Path fails unexpectedly then make sure SMB2=1 (or other SMB setting) is set on both client and target server.
MORE INFO
IMPORTANT SMB NOTE: Both current system and target system must have at least on common SMB protocol enabled for Test-Path to succeed. (SMB2 or later strongly recommended.) For example, if target has SMB1 enabled + SMB2 disabled and client has only SMB2 enabled then logic above will return "Server not found...". This threw me off track until I finally checked my target server (Win7) and found it had SMB2=0 (disabled) and no entry for SMB1 (enabled by default). I fixed by setting SMB2=1 per article below.
SMB OS-specific and scripting details: https://support.microsoft.com/en-us/help/2696547/detect-enable-disable-smbv1-smbv2-smbv3-in-windows-and-windows-server
Excerpt: Win8/Win20012
Detect: Get-SmbServerConfiguration | Select EnableSMB1Protocol
Disable: Set-SmbServerConfiguration -EnableSMB1Protocol $false
Enable: Set-SmbServerConfiguration -EnableSMB1Protocol $true
Excerpt: Win7/Win2008R2Server
Detect:
Get-Item HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters | ForEach-Object {Get-ItemProperty $_.pspath}
Default configuration = Enabled (No registry key is created), so no SMB1 value will be returned
Disable:
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" SMB1 -Type DWORD -Value 0 –Force
Enable:
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" SMB1 -Type DWORD -Value 1 –Force
Code sample: Copy-Item a folder (recursive) only if target server exists
$scriptRootPath="."
$scriptToolsPath="$scriptRootPath\Tools"
$targetServerList="$scriptToolsPath\DeployServerList-INT-POC.txt" #DeployServerList-INT.txt, DeployServerList-QA.txt, DeployServerList-PROD.txt
$stageTargetDrive="c"
$stageFolderPath="$stageTargetDrive$\staging"
$VerbosePreference="Continue" #"SilentlyContinue" (default), "Continue", "Stop", "Inquire"
$InformationPreference="Continue"
Write-Host "Getting list of servers from $targetServerList to stage deployment files to..."
$serverList = (get-content "$targetServerList")
Write-Verbose "scriptToolsPath=$scriptToolsPath"
Write-Verbose "serverlist=$serverList"
Write-Verbose "stageFolderPath=$StageFolderPath"
Write-Host -Separator "-"
Read-Host -Prompt "READY TO STAGE FILES: Check info above, then press Enter to continue (or Ctrl+C to exit)."
Write-Host "-------------------------------------------------"
Write-Host "Staging files to $stageFolderPath on each target server..."
foreach ($server in $serverlist) {
# Input validation
if([string]::IsNullOrWhiteSpace($server)) {continue}
if($server.StartsWith("#")) {Write-Verbose "Comment skipped: $server"; continue} # Skip line if line begins with hashtag comment char
Write-Verbose "Testing filesystem access to $server..."
if(-Not(Test-Path "filesystem::\\$server\$stageTargetDrive$")) {Write-Error "Server not found: $server"; continue}
# TIP: If Test-Path returns false unexpectedly then check if SMB2 is enabled on target server, check SMB1 disabled for both src and target servers.
Write-Verbose "Staging files to $server..."
Copy-Item ".\" -Destination "\\$server\$stageFolderPath" -Recurse -Force -ErrorAction Continue
Write-Information "Files staged on $server."
}