Copying to the clipboard in PowerShell without a new line - powershell

Is there a way to remove a new line from out-clipboard or clip in PowerShell?
I'm using this code to copy current path to clipboard:
function cl() {
(Get-Location).ToString() | clip
}
And every time I use this, a new line is added to the copied text. It's frustrating, because then I can't paste it in the CLI, like I would with text that is copied from elsewhere. Because a new line makes a command on the CLI automatically executed.
Example: I'm in C:\Users and type cl, and then I use Alt + SPACE + E + P to pass the text, the command is executed, and I can't type any more. But when text is passed without a new line nothing is executed, and I can continue to type.

Use the Set-Clipboard function:
(get-location).ToString()|Set-Clipboard

Add-Type -Assembly PresentationCore
$clipText = (get-location).ToString() | Out-String -Stream
[Windows.Clipboard]::SetText($clipText)

As pointed out by #PetSerAl in the comments, the newline is added by PowerShell when the string object is sent through the pipeline. The stringified output of Get-Location does not have that trailing newline:
PS C:\> $v = (Get-Location).ToString()
PS C:\> "-$v-"
-C:\-
You could try something like this:
Add-Type -AssemblyName System.Windows.Forms
$tb = New-Object Windows.Forms.TextBox
$tb.MultiLine = $true
$tb.Text = (Get-Location).ToString()
$tb.SelectAll()
$tb.Copy()

Ending the string with a null byte will take care of it. Useful for powershell core, which doesn't contain Set-Clipboard
function set-clipboard{
param(
[parameter(position=0,mandatory=$true,ValueFromPipeline=$true)]$Text
)
begin{
$data = [system.text.stringbuilder]::new()
}
process{
if ($text){
[void]$data.appendline($text)
}
}
end{
if ($data){
$data.tostring().trimend([environment]::newline) + [convert]::tochar(0) | clip.exe
}
}
}
"asdf" | set-clipboard

Related

Powershell Forms Texbox Multine Output Issue

Racking my brains here, and i'm sure it's something simple.
I've built this simple form app that runs an exe in cmd.
The problem is when i click the build iso button the function runs but only outputs the first line of the .exe and the rest of the output is passed to the terminal and not in the textbox?.
It's almost as if the textbox won't accept more than one line, even though i have multiline set to $true?
The yellow text in terminal should be in the textbox as well:
Code:
Function xiso_build {
Set-Location -Path $PSScriptRoot # change to root folder of this script wherever it's run from
$outputbox.text = & .\extract-xiso.exe -r $selected_file | out-string
}
# Output of xtract-iso textbox
$global:outputBox = New-Object System.Windows.Forms.TextBox #creating the text box
$outputBox.Location = '10,150' #location of the text box (px) in relation to the primary window's edges (length, height)
$outputBox.Size = New-Object System.Drawing.Size(565,200) #the size in px of the text box (length, height)
$outputBox.MultiLine = $True #declaring the text box as multi-line
$outputBox.ScrollBars = "Vertical" #adding scroll bars if required
$form.Controls.Add($outputBox) #activating the text box inside the primary window
# Build Iso Button
$build_button = New-Object System.Windows.Forms.button
$build_button.Text = 'Build ISO'
$build_button.Size = '200,50'
$build_button.Location = '10,360'
# $button.Anchor = 'Bottom,left' # uncomment to move button down to bottom left of app window
$form.Controls.Add($build_button)
$build_button.Add_Click({xiso_build }) # run 'xiso_build' func from above
I'm at a loss here as to have all output in textbox. Thanks all
Theo has provided the crucial pointer: In order to capture all output from an external program, you must also capture its stderr output - by default, only stdout output is captured by PowerShell.
To that end:
use redirection 2>&1 to merge stderr output into the success output stream.
convert all input lines to strings with ForEach-Object ToString (which calls the .ToString() method on each line).
This is necessary, because PowerShell wraps stderr lines in System.Management.Automation.ErrorRecord instances, which Out-String renders as if they were PowerShell errors. Strictly speaking, you only need this in Windows PowerShell; in PowerShell (Core) 7+, even though the wrapping still happens, rendering is by the wrapped line only.
then pipe to Out-String to form a single, multi-line string from all input lines.
$outputbox.text = & .\extract-xiso.exe -r $selected_file 2>&1 |
ForEach-Object ToString | # no longer needed in PS 7+
Out-String
A more concise alternative with a [string[]] cast and the -join operator:
# [string[]] is no longer needed in PS 7+
$outputbox.text =
[string[]] (& .\extract-xiso.exe -r $selected_file 2>&1) -join "`n"
Again, the [string[]] cast isn't needed in PowerShell (Core) 7+ anymore (analogous to how ForEach-Object ToString isn't needed anymore above).
The above doesn't add a trailing newline to the result (though you could achieve that with (... -join "`n") + "`n"), whereas Out-String invariably does.[1]
[1] Arguably, Out-String should not do that; see GitHub issue #14444 for a discussion.

Strange behavior of PSConsoleReadLine

Trying to seemingly generate text in Powershell console (result predetermined), did not like cls + Write-Host because of constant blinking, tried to use PSConsoleReadLine. Code below works in some situations, while it does not in others.
Code works when:
directly copy pasted into Powershell window
when stored in a script file and the file is called from Powershell window
Code does not work when:
launched in ISE (known issue)
right click on the script file / run with powershell
Tried to get around the error with Add-Type, but it ends with IOException and null reference instead. Is present in code below, but commented.
Tried to write a script in a way that relaunches itself in a new Powershell process to handle it, did not help.
Code itself (probably could reproduce the issue with a single line of code, but the guide says one line of a code is not enough):
function tryOne($oldExpression, $targetExpression, $currentPosition, $src)
{
While($targetExpression[$currentPosition] -cne $triedCharacter)
{
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()#here problem
$triedCharacter = Get-Random -InputObject $src
$expression = $oldExpression + $triedCharacter
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($expression)#here problem
Start-sleep -Milliseconds 50
}
Return $expression
}
cd $PSScriptRoot
#Add-Type -path "C:\Program Files\WindowsPowerShell\Modules\PSReadline\1.2\Microsoft.PowerShell.PSReadLine.dll"
#above helps with not found error, with it uncommented the errors at RevertLine and Insert are IOException and null reference
$targetExpression = "Not ideal"
$oldExpression = ""
$currentPosition = 0
#region AllowedCharacters
$src = New-Object System.Collections.ArrayList
for($i=65;$i-le 90;$i++)
{
$src.Add([char]$i) | Out-Null
}
for($i=97;$i-le 122;$i++)
{
$src.Add([char]$i) | Out-Null
}
for($i=0;$i-le 9;$i++)
{
$src.Add($i) | Out-Null
}
$src.Add(" ") | Out-Null #allowed characters in the target sentence - A-Z, a-z, 0-9, space
#endregion
While($currentPosition -lt ($targetExpression.length))
{
$oldExpression = tryOne $oldExpression $targetExpression $currentPosition $src
$currentPosition++;
}
I'd like to be able to launch the script via right click / run with Powershell.

Get-Help format is different when calling it in a script

I am wondering why my PowerShell get-help outputs like the following image when using a script I've written. The script's purpose is to display get-help information when selecting a function from an array.
#Run this file in the same directory as the Functions file.
#this function validates user input
function getInput
{
do
{
$input = Read-Host "`n>Enter function # to see its description"
}until(([int]$input -gt 0) -and ([int]$input -le $flist.count))
$input
}
#include the script we want
. "$PSScriptRoot\functions.ps1"
#This operates on a loop. After viewing your help info, press key and you will be prompted to choose another function.
$quit = 0
while(!$quit){
#get all functions
$f = #(get-content functions.ps1 | where-object { $_.StartsWith("function", "CurrentCultureIgnoreCase") -and (-not $_.Contains("#")); $c++} | sort-object)
"There are " + $f.count + " functions!"
#split on ' ', get second word (function name), add to array
$flist = #{}
$i = 0
foreach($line in $f){
$temp = $line.split(' ')
$temp[1]
$i++
$flist.add($i, $temp[1])
}
#print, order ascending
$flist.GetEnumerator() | sort -Property name
#accept user input
$input = getInput
#get-help about the chosen function
"Get-Help " + $flist[[int]$input]
Get-Help Add-ADGrouptoLocalGroup | format-list
#Get-Help $flist[[int]$input] -full
Get-Command $flist[[int]$input] -syntax
Pause
}
The target script $PSScriptRoot\Functions.ps1 has a bunch of functions in it. What my script is doing is this:
List all functions found within a target file.
Put their name in an indexed array
Prompt user for which function to get-help on, at a given index
Print get-help and get-syntax on the selected function
Each function has the <#.SYNOPSIS .DESCRIPTION ... etc #> comment block in it (You can see the function's details--from the function's comment help-block--in the provided image). If I run get-help on the function within the target script, it appears to be formatted normally--but that's not the case when using script I've written.
What is really bothering me is the #{Text = 'stuff'} formatting, etc. Thanks ahead of time!
You're piping the output of get-help through format-list. This "overrides" the default formatting PS does on the PSCustomObject (in PS 3.0 at least) that get-help creates. You should be able to just invoke get-help by itself and not pipe it. If that doesn't work, then pipe it through out-default.
See help about_format for more details.

Powershell script to read a file line by line, pass the lines which start with a particular string to a function

I'm trying to get code coverage using Sonarqube. The coverage report is generated by karma. For some reason, the coverage file generated by Karma changes
the case of 22 files inside the report. As a result, I'm unable to get coverage for those 22 files. I use a PowerShell script in my Jenkins to generate a canonical path. Below is the script. My script should perform the below steps:
Access the coverage report (unit-tests-lcov.info)
Read the report line by line
Use every file inside unit-tests-lcov.info starting with 'SF' and pass it to the canonical function
Save the file
I'm unable to write a script for the 3rd step. Can anyone make necessary changes to my script below?
$getPathNameSignature = #'
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern uint GetLongPathName(
string shortPath,
StringBuilder sb,
int bufferSize);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]
public static extern uint GetShortPathName(
string longPath,
StringBuilder shortPath,
uint bufferSize);
'#
$getPathNameType = Add-Type -MemberDefinition $getPathNameSignature -Name GetPathNameType -UsingNamespace System.Text -PassThru
function Get-PathCanonicalCase
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]
# Gets the real case of a path
$Path
)
if( -not (Test-Path $Path) )
{
Write-Error "Path '$Path' doesn't exist."
return
}
$shortBuffer = New-Object Text.StringBuilder ($Path.Length * 2)
[void] $getPathNameType::GetShortPathName( $Path, $shortBuffer, $shortBuffer.Capacity )
$longBuffer = New-Object Text.StringBuilder ($Path.Length * 2)
[void] $getPathNameType::GetLongPathName( $shortBuffer.ToString(), $longBuffer, $longBuffer.Capacity )
return $longBuffer.ToString()
}
$file3 = "$env:WORKSPACE\UIArtifacts\unit-tests-lcov.info"
$text = (Get-Content -Path $file3 -ReadCount 0) -join "`n"
$ran = $text -Includes 'SF'
Get-PathCanonicalCase($text) | Set-Content -Path $file3
A part of the input file looks like:
I need to pass the file paths to the Get-Canonical function. PS. Part of the file paths is the environment variable.
TN:
c:\sysapps\hudson\.jenkins\jobs\CropObsUi-Metrics\workspace\encirca\encConf.js
FNF:0
FNH:0
DA:10,1
DA:14,1
DA:30,1
DA:31,1
DA:32,1
DA:33,1
DA:34,1
DA:35,1
DA:36,1
DA:37,1
DA:39,1
LF:11
LH:11
BRF:0
BRH:0
end_of_record
TN:
c:\sysapps\hudson\.jenkins\jobs\CropObsUi-Metrics\workspace\encirca\common\util\data.js
FN:25,(anonymous_1)
FN:57,(anonymous_2)
FN:87,(anonymous_3)
FN:149,(anonymous_4)
FNF:4
FNH:0
FNDA:0,(anonymous_1)
FNDA:0,(anonymous_2)
FNDA:0,(anonymous_3)
FNDA:0,(anonymous_4)
Ok, short list of issues. I see no reason for the -join command. Normally the Get-Content cmdlet will read a text file in as an array of strings, with each line being one string. When you join them it is then converted to one multi-line string. That is totally opposed to your purposes.
$text = Get-Content -Path $file3
You can filter the lines using a Where statement, and the -like operator.
$ran = $text | Where{$_ -like "SF*"}
When you call a function the correct format is normally:
FunctionName -Parameter Value [-AdditionalParameters AdditionalValues]
You can leave out the parameter names and just put the values in order in most cases. So your last line should be:
Get-PathCanonicalCase $ran | Set-Content -Path $file3
That would only output the lines that started with SF though, and I'm not sure how that's going to work since I don't think a path is going to start with SF. I have a feeling that there is more to the line, and this is not going to deal with your problem like you expect it to. That function expects the string that is passed to it to be a path, and only a path. It does not expect to have to parse a path out of a longer string.
OK to pass to the function:
c:\temp\somefile.csv
Not OK to pass to the function:
SF: c:\temp\somefile.csv <8,732 KB> 11/3/2015 08:16:32.6635
I have no idea what your lines look like in your file, so I just randomly made that up, but the point is that the function is not going to work if your path is a substring of what you are passing to the function. I think you are going to need some additional logic to make this work.
But, this does answer your question as to how to pass each line of the file that starts with SF to the function.
Edit2: Ok, I think you were probably better off before you remove the SF: from the lines with a path in them. Here's why... SF: makes it easy to know what lines need to be passed to the function, while the others can be simply passed through. Trimming the "SF: " off the beginning is easy. So, we're going to use RegEx to replace the path with the updated path that the function provides. We're going to use the 'SF: ' to figure out where the paths are. Here we go...
First import the file just like you were, but don't -join it (explained above).
$text = Get-Content -Path $file3
Then we're going to skip the whole $ran = bit, because there's no need for it. Instead we pipe $text into a ForEach loop, and in that loop look at the line. If the line starts with SF: we replace it with "SF:" followed by the output of the function. For the function we send it a substring starting at the 4th character for the current line, so it skips the 'SF:' and only gets the path. If it isn't a SF: line we simply output the line unchanged.
$text |%{If($_ -like "SF:*"){"SF:$(Get-PathCanonicalCase $_.substring(3))"}else{$_}} | Out-File $file3

PowerShell script to check an application that's locking a file?

Using in PowerShell, how can I check if an application is locking a file?
I like to check which process/application is using the file, so that I can close it.
You can do this with the SysInternals tool handle.exe. Try something like this:
PS> $handleOut = handle
PS> foreach ($line in $handleOut) {
if ($line -match '\S+\spid:') {
$exe = $line
}
elseif ($line -match 'C:\\Windows\\Fonts\\segoeui\.ttf') {
"$exe - $line"
}
}
MSASCui.exe pid: 5608 ACME\hillr - 568: File (---) C:\Windows\Fonts\segoeui.ttf
...
This could help you: Use PowerShell to find out which process locks a file. It parses the System.Diagnostics.ProcessModuleCollection Modules property of each process and it looks for the file path of the locked file:
$lockedFile="C:\Windows\System32\wshtcpip.dll"
Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq $lockedFile){$processVar.Name + " PID:" + $processVar.id}}}
You should be able to use the openfiles command from either the regular command line or from PowerShell.
The openfiles built-in tool can be used for file shares or for local files. For local files, you must turn on the tool and restart the machine (again, just for first time use). I believe the command to turn this feature on is:
openfiles /local on
For example (works on Windows Vista x64):
openfiles /query | find "chrome.exe"
That successfully returns file handles associated with Chrome. You can also pass in a file name to see the process currently accessing that file.
You can find a solution using Sysinternal's Handle utility.
I had to modify the code (slightly) to work with PowerShell 2.0:
#/* http://jdhitsolutions.com/blog/powershell/3744/friday-fun-find-file-locking-process-with-powershell/ */
Function Get-LockingProcess {
[cmdletbinding()]
Param(
[Parameter(Position=0, Mandatory=$True,
HelpMessage="What is the path or filename? You can enter a partial name without wildcards")]
[Alias("name")]
[ValidateNotNullorEmpty()]
[string]$Path
)
# Define the path to Handle.exe
# //$Handle = "G:\Sysinternals\handle.exe"
$Handle = "C:\tmp\handle.exe"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\b(\d+)\b)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# (?m) for multiline matching.
# It must be . (not \.) for user group.
[regex]$matchPattern = "(?m)^(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+(?<User>.+)\s+\w+:\s+(?<Path>.*)$"
# skip processing banner
$data = &$handle -u $path -nobanner
# join output for multi-line matching
$data = $data -join "`n"
$MyMatches = $matchPattern.Matches( $data )
# //if ($MyMatches.value) {
if ($MyMatches.count) {
$MyMatches | foreach {
[pscustomobject]#{
FullName = $_.groups["Name"].value
Name = $_.groups["Name"].value.split(".")[0]
ID = $_.groups["PID"].value
Type = $_.groups["Type"].value
User = $_.groups["User"].value.trim()
Path = $_.groups["Path"].value
toString = "pid: $($_.groups["PID"].value), user: $($_.groups["User"].value), image: $($_.groups["Name"].value)"
} #hashtable
} #foreach
} #if data
else {
Write-Warning "No matching handles found"
}
} #end function
Example:
PS C:\tmp> . .\Get-LockingProcess.ps1
PS C:\tmp> Get-LockingProcess C:\tmp\foo.txt
Name Value
---- -----
ID 2140
FullName WINWORD.EXE
toString pid: 2140, user: J17\Administrator, image: WINWORD.EXE
Path C:\tmp\foo.txt
Type File
User J17\Administrator
Name WINWORD
PS C:\tmp>
I was looking for a solution to this as well and hit some hiccups.
Didn't want to use an external app
Open Files requires the local ON attribute which meant systems had to be configured to use it before execution.
After extensive searching I found.
https://github.com/pldmgg/misc-powershell/blob/master/MyFunctions/PowerShellCore_Compatible/Get-FileLockProcess.ps1
Thanks to Paul DiMaggio
This seems to be pure powershell and .net / C#
You can find for your path on handle.exe.
I've used PowerShell but you can do with another command line tool.
With administrative privileges:
handle.exe -a | Select-String "<INSERT_PATH_PART>" -context 0,100
Down the lines and search for "Thread: ...", you should see there the name of the process using your path.
Posted a PowerShell module in PsGallery to discover & kill processes that have open handles to a file or folder.
It exposes functions to: 1) find the locking process, and 2) kill the locking process.
The module automatically downloads handle.exe on first usage.
Find-LockingProcess()
Retrieves process information that has a file handle open to the specified path.
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA | Get-Process
Stop-LockingProcess()
Kills all processes that have a file handle open to the specified path.
Example: Stop-LockingProcess -Path $Home\Documents
PsGallery Link: https://www.powershellgallery.com/packages/LockingProcessKiller
To install run:
Install-Module -Name LockingProcessKiller
I like what the command prompt (CMD) has, and it can be used in PowerShell as well:
tasklist /m <dllName>
Just note that you can't enter the full path of the DLL file. Just the name is good enough.
I've seen a nice solution at Locked file detection that uses only PowerShell and .NET framework classes:
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate,[System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$obj = New-Object Object
$obj | Add-Member Noteproperty FilePath -value $filePath
$obj | Add-Member Noteproperty IsLocked -value $filelocked
$obj
}
If you modify the above function slightly like below it will return True or False
(you will need to execute with full admin rights)
e.g. Usage:
PS> TestFileLock "c:\pagefile.sys"
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name Filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$filelocked
}