I have a basic script, which will shutdown Windows services and generate a report about their shutdown processes. I also want to include two more columns into my output variable ($table), which will be timestamp values i.e. when shutdown tasks were launched and when they finished. I have no idea how to implement this into my report.
$processlist = #('SQLTELEMETRY$TESTDB', 'MSSQL$TESTDB', 'SQLWRITER')
$get = ''
$table = #{ }
$failed = 0
foreach ($proc in $processlist) {
stop-service -name $proc -force
}
#start-sleep -s 120
foreach ($proc in $processlist) {
$get = get-service $proc -Erroraction ignore
if ($get.Status -eq 'Running') {
$table += #{$proc = 'Running' }
}
else {
$table += #{$proc = 'Stopped' }
}
}
foreach ($value in $table.GetEnumerator()) {
if ($value.Value -eq 'Running') {
$failed += 1
}
}
if ($failed -gt 0) {
$err = 'FAILED'
}
else {
$err = 'SUCCESS'
}
$table.GetEnumerator() | Select-Object -Property Name, Value | export-csv appreport.csv -delimiter ";" -force -notypeinformation
(HTML part here...)
Instead of adding stuff into a Hashtable, I think it would be a lot easier to build an array of objects and write that as CSV file.
Something like this:
$serviceList = 'SQLTELEMETRY$TESTDB', 'MSSQL$TESTDB', 'SQLWRITER'
$maxAttempts = 10
# $result will become an array of PsCustomObjects you can easily pipe to Export-Csv
$result = foreach ($service in $serviceList) {
$shutStart = Get-Date
$svc = Get-Service -Name $service -ErrorAction SilentlyContinue
if ($svc) {
for ($attempt = 0; $attempt -lt $maxAttempts; $attempt++) {
$shutResult = 'Failed'
Start-Sleep -Milliseconds 100
$svc | Stop-Service -Force -ErrorAction SilentlyContinue
# test if the service has stopped. If so exit the loop
if (($svc | Get-Service).Status -eq 'Stopped') {
$shutResult = 'Success'
break
}
}
[PsCustomObject]#{
'ServiceName' = $svc.Name
'ServiceDisplayName' = $svc.DisplayName
'ShutDownStart' = $shutStart
'ShutDownEnd' = Get-Date
'Result' = $shutResult
}
}
else {
[PsCustomObject]#{
'ServiceName' = $service
'ServiceDisplayName' = ''
'ShutDownStart' = $shutStart
'ShutDownEnd' = Get-Date
'Result' = "Failed: Service '$service' could not be found."
}
}
}
# output on screen
$result
# output to CSV
$result | Export-Csv 'D:\appreport.csv' -Delimiter ";" -Force -NoTypeInformation
The output on screen will look like this:
ServiceName : SQLTELEMETRY$TESTDB
ServiceDisplayName :
ShutDownStart : 22-8-2019 16:47:40
ShutDownEnd : 22-8-2019 16:47:40
Result : Failed: Service 'SQLTELEMETRY$TESTDB' could not be found.
ServiceName : MSSQL$TESTDB
ServiceDisplayName :
ShutDownStart : 22-8-2019 16:47:40
ShutDownEnd : 22-8-2019 16:47:40
Result : Failed: Service 'MSSQL$TESTDB' could not be found.
ServiceName : SQLWRITER
ServiceDisplayName : SQL Server VSS Writer
ShutDownStart : 22-8-2019 16:47:38
ShutDownEnd : 22-8-2019 16:47:39
Result : Success
Hope that helps
I don't really know when you want to capture the time stamp for the services, but I suggest you take advantage of the below property and add it in the loop where you think its suitable.
(Get-Process -Name $proc).StartTime
Also you can use the below properties :
UserProcessorTime
TotalProcessTime
ExitTime
I hope this will help you to capture to time.
How do redirect
stderr to logfile
stdout to object
Things I've looked at:
>> and 2>> only redirect to file .
-RedirectStandardOutput and -RedirectStandardError only redirect to file again.
| Out-File cannot redirect stderr.
| Tee-Object same issue.
Joining stdout and stderr output streams works like PetSerAl commented, though the syntax is not the most intuitive.
The weirdish syntax of 2>&1 means that stderr (stream 2) is to be added into the stdout (stream 1). As this is not actually what you are after, try adapting the other example from the MS page to Powershell:
Or, you can redirect the output to one place, and the errors to another.
dir file.xxx > output.msg 2> output.err
Thus,
$ret = myCommand 2> errors.log
should send errors in a log file and non-errors in the $ret variable.
A comprehensive explanation in about_Redirection MSDN article.
A Minimal, Complete, and Verifiable example (stdout to pipe):
PS D:\PShell> -1,5,0,2| ForEach-Object { 15/$_ } 2>"$env:temp\err.txt" | Write-Output
-15
3
7.5
PS D:\PShell> Get-Content "$env:temp\err.txt"
Attempted to divide by zero.
At line:1 char:28
+ -1,5,0,2| ForEach-Object { 15/$_ } 2>"$env:temp\err.txt" | Write-Outpu ...
+ ~~~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
PS D:\PShell>
Another example (stdout to object):
PS D:\PShell> $x = -1,5,0,2| ForEach-Object { 15/$_} 2>"$env:temp\err.txt"
PS D:\PShell> $x
-15
3
7.5
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message <#| ConvertTo-Encoding $firstEncNew $secondEncNew#>;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ <#| ConvertTo-Encoding $firstEncNew $secondEncNew#>;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message <#| ConvertTo-Encoding $firstEncNew $secondEncNew#>;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
[Console]::Write("exception:`n");
$r.Meta1
[Console]::Write("stderr:`n");
$r.Meta2
[Console]::Write("stdout:`n");
$r.Meta3
I'm trying to pipe a string into a program's STDIN without any trailing linefeeds (unless that string itself actually ends in a linefeed). I tried googling around, but I only found people trying to print to the console without a trailing linefeed, in which case Write-Host takes a parameter -NoNewLine. However, to pipe it on to another program, I need Write-Output or similar which doesn't have such a parameter. Now it seems like Write-Output isn't even the problem:
Z:\> (Write-Output "abc").Length
3
But as soon as I pipe it to another program and read the string there, I get an additional linefeed. For instance, I tried this Ruby snippet:
Z:\> Write-Output "abc" | ruby -e "p ARGF.read"
"abc\n"
I checked that the actual string received is abc\n. The same happens in several other languages (at least C#, Java and Python), so I believe it's an issue with PowerShell, not the language doing the reading.
As a further test, I replaced Write-Output itself with another Ruby script:
Z:\> ruby -e "$> << 'abc'"
abcZ:\>
(That is, there is definitely no \n on the script's STDOUT.)
But again, when I pipe it into another script:
Z:\> ruby -e "$> << 'abc'" | ruby -e "p ARGF.read"
"abc\n"
I'm fairly convinced that it's the pipe which adds the linefeed. How do I avoid that? I actually want to be able to control whether the input ends in a linefeed or not (by including it in the input or omitting it).
(For reference, I also tested strings which already contain a trailing linefeed, and in that case the pipe doesn't add another one, so I guess it just ensures a trailing linefeed.)
I originally encountered this in PowerShell v3, but I'm now using v5 and still have the same issue.
Introduction
Here is my Invoke-RawPipeline function (get latest version from this Gist).
Use it to pipe binary data between processes' Standard Output and Standard Input streams. It can read input stream from file/pipeline and save resulting output stream to file.
It requires PsAsync module to be able to launch and pipe data in multiple processes.
In case of issues use -Verbose switch to see debug output.
Examples
Redirecting to file
Batch:
findstr.exe /C:"Warning" /I C:\Windows\WindowsUpdate.log > C:\WU_Warnings.txt
PowerShell:
Invoke-RawPipeline -Command #{Path = 'findstr.exe' ; Arguments = '/C:"Warning" /I C:\Windows\WindowsUpdate.log'} -OutFile 'C:\WU_Warnings.txt'
Redirecting from file
Batch:
svnadmin load < C:\RepoDumps\MyRepo.dump
PowerShell:
Invoke-RawPipeline -InFile 'C:\RepoDumps\MyRepo.dump' -Command #{Path = 'svnadmin.exe' ; Arguments = 'load'}
Piping strings
Batch:
echo TestString | find /I "test" > C:\SearchResult.log
PowerShell:
'TestString' | Invoke-RawPipeline -Command #{Path = 'find.exe' ; Arguments = '/I "test"'} -OutFile 'C:\SearchResult.log'
Piping between multiple processes
Batch:
ipconfig | findstr /C:"IPv4 Address" /I
PowerShell:
Invoke-RawPipeline -Command #{Path = 'ipconfig'}, #{Path = 'findstr' ; Arguments = '/C:"IPv4 Address" /I'} -RawData
Code:
<#
.Synopsis
Pipe binary data between processes' Standard Output and Standard Input streams.
Can read input stream from file and save resulting output stream to file.
.Description
Pipe binary data between processes' Standard Output and Standard Input streams.
Can read input stream from file/pipeline and save resulting output stream to file.
Requires PsAsync module: http://psasync.codeplex.com
.Notes
Author: beatcracker (https://beatcracker.wordpress.com, https://github.com/beatcracker)
License: Microsoft Public License (http://opensource.org/licenses/MS-PL)
.Component
Requires PsAsync module: http://psasync.codeplex.com
.Parameter Command
An array of hashtables, each containing Command Name, Working Directory and Arguments
.Parameter InFile
This parameter is optional.
A string representing path to file, to read input stream from.
.Parameter OutFile
This parameter is optional.
A string representing path to file, to save resulting output stream to.
.Parameter Append
This parameter is optional. Default is false.
A switch controlling wheither ovewrite or append output file if it already exists. Default is to overwrite.
.Parameter IoTimeout
This parameter is optional. Default is 0.
A number of seconds to wait if Input/Output streams are blocked. Default is to wait indefinetely.
.Parameter ProcessTimeout
This parameter is optional. Default is 0.
A number of seconds to wait for process to exit after finishing all pipeline operations. Default is to wait indefinetely.
Details: https://msdn.microsoft.com/en-us/library/ty0d8k56.aspx
.Parameter BufferSize
This parameter is optional. Default is 4096.
Size of buffer in bytes for read\write operations. Supports standard Powershell multipliers: KB, MB, GB, TB, and PB.
Total number of buffers is: Command.Count * 2 + InFile + OutFile.
.Parameter ForceGC
This parameter is optional.
A switch, that if specified will force .Net garbage collection.
Use to immediately release memory on function exit, if large buffer size was used.
.Parameter RawData
This parameter is optional.
By default function returns object with StdOut/StdErr streams and process' exit codes.
If this switch is specified, function will return raw Standard Output stream.
.Example
Invoke-RawPipeline -Command #{Path = 'findstr.exe' ; Arguments = '/C:"Warning" /I C:\Windows\WindowsUpdate.log'} -OutFile 'C:\WU_Warnings.txt'
Batch analog: findstr.exe /C:"Warning" /I C:\Windows\WindowsUpdate.log' > C:\WU_Warnings.txt
.Example
Invoke-RawPipeline -Command #{Path = 'findstr.exe' ; WorkingDirectory = 'C:\Windows' ; Arguments = '/C:"Warning" /I .\WindowsUpdate.log'} -RawData
Batch analog: cd /D C:\Windows && findstr.exe /C:"Warning" /I .\WindowsUpdate.log
.Example
'TestString' | Invoke-RawPipeline -Command #{Path = 'find.exe' ; Arguments = '/I "test"'} -OutFile 'C:\SearchResult.log'
Batch analog: echo TestString | find /I "test" > C:\SearchResult.log
.Example
Invoke-RawPipeline -Command #{Path = 'ipconfig'}, #{Path = 'findstr' ; Arguments = '/C:"IPv4 Address" /I'} -RawData
Batch analog: ipconfig | findstr /C:"IPv4 Address" /I
.Example
Invoke-RawPipeline -InFile 'C:\RepoDumps\Repo.svn' -Command #{Path = 'svnadmin.exe' ; Arguments = 'load'}
Batch analog: svnadmin load < C:\RepoDumps\MyRepo.dump
#>
function Invoke-RawPipeline
{
[CmdletBinding()]
Param
(
[Parameter(ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()]
[ValidateScript({
if($_.psobject.Methods.Match.('ToString'))
{
$true
}
else
{
throw 'Can''t convert pipeline object to string!'
}
})]
$InVariable,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateScript({
$_ | ForEach-Object {
$Path = $_.Path
$WorkingDirectory = $_.WorkingDirectory
if(!(Get-Command -Name $Path -CommandType Application -ErrorAction SilentlyContinue))
{
throw "Command not found: $Path"
}
if($WorkingDirectory)
{
if(!(Test-Path -LiteralPath $WorkingDirectory -PathType Container -ErrorAction SilentlyContinue))
{
throw "Working directory not found: $WorkingDirectory"
}
}
}
$true
})]
[ValidateNotNullOrEmpty()]
[array]$Command,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateScript({
if(!(Test-Path -LiteralPath $_))
{
throw "File not found: $_"
}
$true
})]
[ValidateNotNullOrEmpty()]
[string]$InFile,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateScript({
if(!(Test-Path -LiteralPath (Split-Path $_)))
{
throw "Folder not found: $_"
}
$true
})]
[ValidateNotNullOrEmpty()]
[string]$OutFile,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[switch]$Append,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateRange(0, 2147483)]
[int]$IoTimeout = 0,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateRange(0, 2147483)]
[int]$ProcessTimeout = 0,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[long]$BufferSize = 4096,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[switch]$RawData,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[switch]$ForceGC
)
Begin
{
$Modules = #{PsAsync = 'http://psasync.codeplex.com'}
'Loading modules:', ($Modules | Format-Table -HideTableHeaders -AutoSize | Out-String) | Write-Verbose
foreach($module in $Modules.GetEnumerator())
{
if(!(Get-Module -Name $module.Key))
{
Try
{
Import-Module -Name $module.Key -ErrorAction Stop
}
Catch
{
throw "$($module.Key) module not available. Get it here: $($module.Value)"
}
}
}
function New-ConsoleProcess
{
Param
(
[string]$Path,
[string]$Arguments,
[string]$WorkingDirectory,
[switch]$CreateNoWindow = $true,
[switch]$RedirectStdIn = $true,
[switch]$RedirectStdOut = $true,
[switch]$RedirectStdErr = $true
)
if(!$WorkingDirectory)
{
if(!$script:MyInvocation.MyCommand.Path)
{
$WorkingDirectory = [System.AppDomain]::CurrentDomain.BaseDirectory
}
else
{
$WorkingDirectory = Split-Path $script:MyInvocation.MyCommand.Path
}
}
Try
{
$ps = New-Object -TypeName System.Diagnostics.Process -ErrorAction Stop
$ps.StartInfo.Filename = $Path
$ps.StartInfo.Arguments = $Arguments
$ps.StartInfo.UseShellExecute = $false
$ps.StartInfo.RedirectStandardInput = $RedirectStdIn
$ps.StartInfo.RedirectStandardOutput = $RedirectStdOut
$ps.StartInfo.RedirectStandardError = $RedirectStdErr
$ps.StartInfo.CreateNoWindow = $CreateNoWindow
$ps.StartInfo.WorkingDirectory = $WorkingDirectory
}
Catch
{
throw $_
}
return $ps
}
function Invoke-GarbageCollection
{
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
}
$CleanUp = {
$IoWorkers + $StdErrWorkers |
ForEach-Object {
$_.Src, $_.Dst |
ForEach-Object {
if(!($_ -is [System.Diagnostics.Process]))
{
Try
{
$_.Close()
}
Catch
{
Write-Error "Failed to close $_"
}
$_.Dispose()
}
}
}
}
$PumpData = {
Param
(
[hashtable]$Cfg
)
# Fail hard, we don't want stuck threads
$Private:ErrorActionPreference = 'Stop'
$Src = $Cfg.Src
$SrcEndpoint = $Cfg.SrcEndpoint
$Dst = $Cfg.Dst
$DstEndpoint = $Cfg.DstEndpoint
$BufferSize = $Cfg.BufferSize
$SyncHash = $Cfg.SyncHash
$RunspaceId = $Cfg.Id
# Setup Input and Output streams
if($Src -is [System.Diagnostics.Process])
{
switch ($SrcEndpoint)
{
'StdOut' {$InStream = $Src.StandardOutput.BaseStream}
'StdIn' {$InStream = $Src.StandardInput.BaseStream}
'StdErr' {$InStream = $Src.StandardError.BaseStream}
default {throw "Not valid source endpoint: $_"}
}
}
else
{
$InStream = $Src
}
if($Dst -is [System.Diagnostics.Process])
{
switch ($DstEndpoint)
{
'StdOut' {$OutStream = $Dst.StandardOutput.BaseStream}
'StdIn' {$OutStream = $Dst.StandardInput.BaseStream}
'StdErr' {$OutStream = $Dst.StandardError.BaseStream}
default {throw "Not valid destination endpoint: $_"}
}
}
else
{
$OutStream = $Dst
}
$InStream | Out-String | ForEach-Object {$SyncHash.$RunspaceId.Status += "InStream: $_"}
$OutStream | Out-String | ForEach-Object {$SyncHash.$RunspaceId.Status += "OutStream: $_"}
# Main data copy loop
$Buffer = New-Object -TypeName byte[] $BufferSize
$BytesThru = 0
Try
{
Do
{
$SyncHash.$RunspaceId.IoStartTime = [DateTime]::UtcNow.Ticks
$ReadCount = $InStream.Read($Buffer, 0, $Buffer.Length)
$OutStream.Write($Buffer, 0, $ReadCount)
$OutStream.Flush()
$BytesThru += $ReadCount
}
While($readCount -gt 0)
}
Catch
{
$SyncHash.$RunspaceId.Status += $_
}
Finally
{
$OutStream.Close()
$InStream.Close()
}
}
}
Process
{
$PsCommand = #()
if($Command.Length)
{
Write-Verbose 'Creating new process objects'
$i = 0
foreach($cmd in $Command.GetEnumerator())
{
$PsCommand += New-ConsoleProcess #cmd
$i++
}
}
Write-Verbose 'Building I\O pipeline'
$PipeLine = #()
if($InVariable)
{
[Byte[]]$InVarBytes = [Text.Encoding]::UTF8.GetBytes($InVariable.ToString())
$PipeLine += New-Object -TypeName System.IO.MemoryStream -ArgumentList $BufferSize -ErrorAction Stop
$PipeLine[-1].Write($InVarBytes, 0, $InVarBytes.Length)
[Void]$PipeLine[-1].Seek(0, 'Begin')
}
elseif($InFile)
{
$PipeLine += New-Object -TypeName System.IO.FileStream -ArgumentList ($InFile, [IO.FileMode]::Open) -ErrorAction Stop
if($PsCommand.Length)
{
$PsCommand[0].StartInfo.RedirectStandardInput = $true
}
}
else
{
if($PsCommand.Length)
{
$PsCommand[0].StartInfo.RedirectStandardInput = $false
}
}
$PipeLine += $PsCommand
if($OutFile)
{
if($PsCommand.Length)
{
$PsCommand[-1].StartInfo.RedirectStandardOutput = $true
}
if($Append)
{
$FileMode = [System.IO.FileMode]::Append
}
else
{
$FileMode = [System.IO.FileMode]::Create
}
$PipeLine += New-Object -TypeName System.IO.FileStream -ArgumentList ($OutFile, $FileMode, [System.IO.FileAccess]::Write) -ErrorAction Stop
}
else
{
if($PsCommand.Length)
{
$PipeLine += New-Object -TypeName System.IO.MemoryStream -ArgumentList $BufferSize -ErrorAction Stop
}
}
Write-Verbose 'Creating I\O threads'
$IoWorkers = #()
for($i=0 ; $i -lt ($PipeLine.Length-1) ; $i++)
{
$SrcEndpoint = $DstEndpoint = $null
if($PipeLine[$i] -is [System.Diagnostics.Process])
{
$SrcEndpoint = 'StdOut'
}
if($PipeLine[$i+1] -is [System.Diagnostics.Process])
{
$DstEndpoint = 'StdIn'
}
$IoWorkers += #{
Src = $PipeLine[$i]
SrcEndpoint = $SrcEndpoint
Dst = $PipeLine[$i+1]
DstEndpoint = $DstEndpoint
}
}
Write-Verbose "Created $($IoWorkers.Length) I\O worker objects"
Write-Verbose 'Creating StdErr readers'
$StdErrWorkers = #()
for($i=0 ; $i -lt $PsCommand.Length ; $i++)
{
$StdErrWorkers += #{
Src = $PsCommand[$i]
SrcEndpoint = 'StdErr'
Dst = New-Object -TypeName System.IO.MemoryStream -ArgumentList $BufferSize -ErrorAction Stop
}
}
Write-Verbose "Created $($StdErrWorkers.Length) StdErr reader objects"
Write-Verbose 'Starting processes'
$PsCommand |
ForEach-Object {
$ps = $_
Try
{
[void]$ps.Start()
}
Catch
{
Write-Error "Failed to start process: $($ps.StartInfo.FileName)"
Write-Verbose "Can't launch process, killing and disposing all"
if($PsCommand)
{
$PsCommand |
ForEach-Object {
Try{$_.Kill()}Catch{} # Can't do much if kill fails...
$_.Dispose()
}
}
Write-Verbose 'Closing and disposing I\O streams'
. $CleanUp
}
Write-Verbose "Started new process: Name=$($ps.Name), Id=$($ps.Id)"
}
$WorkersCount = $IoWorkers.Length + $StdErrWorkers.Length
Write-Verbose 'Creating sync hashtable'
$sync = #{}
for($i=0 ; $i -lt $WorkersCount ; $i++)
{
$sync += #{$i = #{IoStartTime = $nul ; Status = $null}}
}
$SyncHash = [hashtable]::Synchronized($sync)
Write-Verbose 'Creating runspace pool'
$RunspacePool = Get-RunspacePool $WorkersCount
Write-Verbose 'Loading workers on the runspace pool'
$AsyncPipelines = #()
$i = 0
$IoWorkers + $StdErrWorkers |
ForEach-Object {
$Param = #{
BufferSize = $BufferSize
Id = $i
SyncHash = $SyncHash
} + $_
$AsyncPipelines += Invoke-Async -RunspacePool $RunspacePool -ScriptBlock $PumpData -Parameters $Param
$i++
Write-Verbose 'Started working thread'
$Param | Format-Table -HideTableHeaders -AutoSize | Out-String | Write-Debug
}
Write-Verbose 'Waiting for I\O to complete...'
if($IoTimeout){Write-Verbose "Timeout is $IoTimeout seconds"}
Do
{
# Check for pipelines with errors
[array]$FailedPipelines = Receive-AsyncStatus -Pipelines $AsyncPipelines | Where-Object {$_.Completed -and $_.Error}
if($FailedPipelines)
{
"$($FailedPipelines.Length) pipeline(s) failed!",
($FailedPipelines | Select-Object -ExpandProperty Error | Format-Table -AutoSize | Out-String) | Write-Debug
}
if($IoTimeout)
{
# Compare I\O start time of thread with current time
[array]$LockedReaders = $SyncHash.Keys | Where-Object {[TimeSpan]::FromTicks([DateTime]::UtcNow.Ticks - $SyncHash.$_.IoStartTime).TotalSeconds -gt $IoTimeout}
if($LockedReaders)
{
# Yikes, someone is stuck
"$($LockedReaders.Length) I\O operations reached timeout!" | Write-Verbose
$SyncHash.GetEnumerator() | ForEach-Object {"$($_.Key) = $($_.Value.Status)"} | Sort-Object | Out-String | Write-Debug
$PsCommand | ForEach-Object {
Write-Verbose "Killing process: Name=$($_.Name), Id=$($_.Id)"
Try
{
$_.Kill()
}
Catch
{
Write-Error 'Failed to kill process!'
}
}
break
}
}
Start-Sleep 1
}
While(Receive-AsyncStatus -Pipelines $AsyncPipelines | Where-Object {!$_.Completed}) # Loop until all pipelines are finished
Write-Verbose 'Waiting for all pipelines to finish...'
$IoStats = Receive-AsyncResults -Pipelines $AsyncPipelines
Write-Verbose 'All pipelines are finished'
Write-Verbose 'Collecting StdErr for all processes'
$PipeStdErr = $StdErrWorkers |
ForEach-Object {
$Encoding = $_.Src.StartInfo.StandardOutputEncoding
if(!$Encoding)
{
$Encoding = [System.Text.Encoding]::Default
}
#{
FileName = $_.Src.StartInfo.FileName
StdErr = $Encoding.GetString($_.Dst.ToArray())
ExitCode = $_.Src.ExitCode
}
} |
Select-Object #{Name = 'FileName' ; Expression = {$_.FileName}},
#{Name = 'StdErr' ; Expression = {$_.StdErr}},
#{Name = 'ExitCode' ; Expression = {$_.ExitCode}}
if($IoWorkers[-1].Dst -is [System.IO.MemoryStream])
{
Write-Verbose 'Collecting final pipeline output'
if($IoWorkers[-1].Src -is [System.Diagnostics.Process])
{
$Encoding = $IoWorkers[-1].Src.StartInfo.StandardOutputEncoding
}
if(!$Encoding)
{
$Encoding = [System.Text.Encoding]::Default
}
$PipeResult = $Encoding.GetString($IoWorkers[-1].Dst.ToArray())
}
Write-Verbose 'Closing and disposing I\O streams'
. $CleanUp
$PsCommand |
ForEach-Object {
$_.Refresh()
if(!$_.HasExited)
{
Write-Verbose "Process is still active: Name=$($_.Name), Id=$($_.Id)"
if(!$ProcessTimeout)
{
$ProcessTimeout = -1
}
else
{
$WaitForExitProcessTimeout = $ProcessTimeout * 1000
}
Write-Verbose "Waiting for process to exit (Process Timeout = $ProcessTimeout)"
if(!$_.WaitForExit($WaitForExitProcessTimeout))
{
Try
{
Write-Verbose 'Trying to kill it'
$_.Kill()
}
Catch
{
Write-Error "Failed to kill process $_"
}
}
}
Write-Verbose "Disposing process object: Name=$($_.StartInfo.FileName)"
$_.Dispose()
}
Write-Verbose 'Disposing runspace pool'
# http://stackoverflow.com/questions/21454252/how-to-cleanup-resources-in-a-dll-when-powershell-ise-exits-like-new-pssession
$RunspacePool.Dispose()
if($ForceGC)
{
Write-Verbose 'Forcing garbage collection'
Invoke-GarbageCollection
}
if(!$RawData)
{
New-Object -TypeName psobject -Property #{Result = $PipeResult ; Status = $PipeStdErr}
}
else
{
$PipeResult
}
}
}
Bruteforce approach: feed binary data to process' stdin. I've tested this code on the cat.exe from UnixUtils and it seems to do what you want:
# Text to send
$InputVar = "No Newline, No NewLine,`nNewLine, No NewLine,`nNewLine, No NewLine"
# Buffer & initial size of MemoryStream
$BufferSize = 4096
# Convert text to bytes and write to MemoryStream
[byte[]]$InputBytes = [Text.Encoding]::UTF8.GetBytes($InputVar)
$MemStream = New-Object -TypeName System.IO.MemoryStream -ArgumentList $BufferSize
$MemStream.Write($InputBytes, 0, $InputBytes.Length)
[Void]$MemStream.Seek(0, 'Begin')
# Setup stdin\stdout redirection for our process
$StartInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo -Property #{
FileName = 'MyLittle.exe'
UseShellExecute = $false
RedirectStandardInput = $true
}
# Create new process
$Process = New-Object -TypeName System.Diagnostics.Process
# Assign previously created StartInfo properties
$Process.StartInfo = $StartInfo
# Start process
[void]$Process.Start()
# Pipe data
$Buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
$StdinStream = $Process.StandardInput.BaseStream
try
{
do
{
$ReadCount = $MemStream.Read($Buffer, 0, $Buffer.Length)
$StdinStream.Write($Buffer, 0, $ReadCount)
$StdinStream.Flush()
}
while($ReadCount -gt 0)
}
catch
{
throw 'Houston, we have a problem!'
}
finally
{
# Close streams
$StdinStream.Close()
$MemStream.Close()
}
# Cleanup
'Process', 'StdinStream', 'MemStream' |
ForEach-Object {
(Get-Variable $_ -ValueOnly).Dispose()
Remove-Variable $_ -Force
}
Do it a simple way create a cmd process and execute it
$cmdArgs = #('/c','something.exe','arg1', .. , 'arg2' , $anotherArg , '<', '$somefile.txt' )
&'cmd.exe' $cmdArgs
Worked perfect for piping information into stdin that I wanted,
To clear up a fundamental misconception in some of the comments: the "powershell commands" in a pipeline are cmdlets and each one runs within the process space of the single powershell. Thus, objects are being passed as is within the same process (on multiple threads) UNLESS you invoke an external command. Then the passed objects are converted to strings by the appropriate formatting cmdlet (if not already string objects). These strings are then converted to a stream of characters with each string having an appended \n. So it is not "the pipeline" adding the \n but the implicit conversion to text for input to the "legacy" command.
The basic problem in the question is that the asker is trying to get object like behaviour (e.g. a string with no trailing \n) on a character (byte) stream input. The standard input stream of a (console) process supplies characters (bytes) one at a time. The input routines collect these individual characters into a single string (typically) terminated when a \n is received. Whether the \n is returned as part of the string is up to the input routine. When the standard input stream is redirected to a file or pipe, the input routines mostly have no knowledge of this. So there is no way to determine the difference between a complete string with no \n and an incomplete string with more characters and the \n still to come.
Possible solutions (to the string delimiting problem, not the powershell added \n problem) would be to have some sort of time-out on the standard input reads. The end of a string could be signaled by no received characters for a certain time. Alternatively, if you had low enough level access to the pipe you could try to have atomic reads and writes. In this way a blocked read would return exactly what was written. Unfortunately, both of these methods have timing problems when running within a multitasking environment. If the delay is long then efficiency falls but if it is too short then it can be fooled by delays caused by process priority scheduling. Scheduling priorities can also interfere with atomic reads and writes if the writing process writes another line before the reading process has read the current one. It would need some sort of synchronizing system.
The only other way to signal that there are no more characters coming on the current line would be to close the pipe (EOF) but this is a once only method so you can only send one string (trailing \n or not). (This is how Ruby knows when the input is finished both in the initial example and in the Invoke-RawPipeline example.) It is possible that this is actually your intention (send only one string with or without trailing \n) in which case you could simply concatenate all the input (retaining or re-inserting any embedded \n) and throw away the last \n.
A possible solution to the powershell added \n problem for multiple strings would be to redefine your encoding of "a string" by terminating each string object with an otherwise invalid character sequence. \0 could be used if you had per character input (no good for C-like line input) otherwise maybe \377 (0xff). This would allow the input routines of your legacy command to "know" when a string ended. The sequence \0\n (or \377\n) would be the "end" of the string and everything before that (including a trailing \n or not, possibly using multiple reads) would be the string. I am assuming that you have some control over the input routines (e.g. you wrote the program) since any off the shelf program reading from standard input would typically be expecting a \n (or EOF) to delimit its input.
I will admit to having zero experience with the ruby -e "puts ARGF.read" command you are using after the pipe, but I think I can prove that the pipe doesn't adding a newline.
# check length of string without newline after pipe
Write-Output "abc" | %{Write-Host "$_ has a length of: $($_.Length)" }
#check of string with newline length after pipe
Write-Output "def`n" | %{Write-Host "$($_.Length) is the length of $_" -NoNewline }
#write a string without newline (suppressing newline on Write-Host)
Write-Output 'abc' | %{ Write-Host $_ -NoNewline; }
#write a string with newline (suppressing newline on Write-Host)
Write-Output "def`n" | %{ Write-Host $_ -NoNewline; }
#write a final string without newline (suppressing newline on Write-Host)
Write-Output 'ghi' | %{ Write-Host $_ -NoNewline; }
This gives me an output of:
abc has a length of: 3
4 is the length of def
abcdef
ghi
I think you might want to start looking at the ruby -e "put AGRF.read" command and see if it is adding a newline after each read.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
What essential things (functions, aliases, start up scripts) do you have in your profile?
I often find myself needing needing some basic agregates to count/sum some things., I've defined these functions and use them often, they work really nicely at the end of a pipeline :
#
# useful agregate
#
function count
{
BEGIN { $x = 0 }
PROCESS { $x += 1 }
END { $x }
}
function product
{
BEGIN { $x = 1 }
PROCESS { $x *= $_ }
END { $x }
}
function sum
{
BEGIN { $x = 0 }
PROCESS { $x += $_ }
END { $x }
}
function average
{
BEGIN { $max = 0; $curr = 0 }
PROCESS { $max += $_; $curr += 1 }
END { $max / $curr }
}
To be able to get time and path with colors in my prompt :
function Get-Time { return $(get-date | foreach { $_.ToLongTimeString() } ) }
function prompt
{
# Write the time
write-host "[" -noNewLine
write-host $(Get-Time) -foreground yellow -noNewLine
write-host "] " -noNewLine
# Write the path
write-host $($(Get-Location).Path.replace($home,"~").replace("\","/")) -foreground green -noNewLine
write-host $(if ($nestedpromptlevel -ge 1) { '>>' }) -noNewLine
return "> "
}
The following functions are stolen from a blog and modified to fit my taste, but ls with colors is very nice :
# LS.MSH
# Colorized LS function replacement
# /\/\o\/\/ 2006
# http://mow001.blogspot.com
function LL
{
param ($dir = ".", $all = $false)
$origFg = $host.ui.rawui.foregroundColor
if ( $all ) { $toList = ls -force $dir }
else { $toList = ls $dir }
foreach ($Item in $toList)
{
Switch ($Item.Extension)
{
".Exe" {$host.ui.rawui.foregroundColor = "Yellow"}
".cmd" {$host.ui.rawui.foregroundColor = "Red"}
".msh" {$host.ui.rawui.foregroundColor = "Red"}
".vbs" {$host.ui.rawui.foregroundColor = "Red"}
Default {$host.ui.rawui.foregroundColor = $origFg}
}
if ($item.Mode.StartsWith("d")) {$host.ui.rawui.foregroundColor = "Green"}
$item
}
$host.ui.rawui.foregroundColor = $origFg
}
function lla
{
param ( $dir=".")
ll $dir $true
}
function la { ls -force }
And some shortcuts to avoid really repetitive filtering tasks :
# behave like a grep command
# but work on objects, used
# to be still be allowed to use grep
filter match( $reg )
{
if ($_.tostring() -match $reg)
{ $_ }
}
# behave like a grep -v command
# but work on objects
filter exclude( $reg )
{
if (-not ($_.tostring() -match $reg))
{ $_ }
}
# behave like match but use only -like
filter like( $glob )
{
if ($_.toString() -like $glob)
{ $_ }
}
filter unlike( $glob )
{
if (-not ($_.tostring() -like $glob))
{ $_ }
}
This iterates through a scripts PSDrive and dot-sources everything that begins with "lib-".
### ---------------------------------------------------------------------------
### Load function / filter definition library
### ---------------------------------------------------------------------------
Get-ChildItem scripts:\lib-*.ps1 | % {
. $_
write-host "Loading library file:`t$($_.name)"
}
To setup my Visual Studio build environment from PowerShell I took the VsVars32 from here. and use it all the time.
###############################################################################
# Exposes the environment vars in a batch and sets them in this PS session
###############################################################################
function Get-Batchfile($file)
{
$theCmd = "`"$file`" & set"
cmd /c $theCmd | Foreach-Object {
$thePath, $theValue = $_.split('=')
Set-Item -path env:$thePath -value $theValue
}
}
###############################################################################
# Sets the VS variables for this PS session to use
###############################################################################
function VsVars32($version = "9.0")
{
$theKey = "HKLM:SOFTWARE\Microsoft\VisualStudio\" + $version
$theVsKey = get-ItemProperty $theKey
$theVsInstallPath = [System.IO.Path]::GetDirectoryName($theVsKey.InstallDir)
$theVsToolsDir = [System.IO.Path]::GetDirectoryName($theVsInstallPath)
$theVsToolsDir = [System.IO.Path]::Combine($theVsToolsDir, "Tools")
$theBatchFile = [System.IO.Path]::Combine($theVsToolsDir, "vsvars32.bat")
Get-Batchfile $theBatchFile
[System.Console]::Title = "Visual Studio " + $version + " Windows Powershell"
}
start-transcript. This will write out your entire session to a text file. Great for training new hires on how to use Powershell in the environment.
My prompt contains:
$width = ($Host.UI.RawUI.WindowSize.Width - 2 - $(Get-Location).ToString().Length)
$hr = New-Object System.String #('-',$width)
Write-Host -ForegroundColor Red $(Get-Location) $hr
Which gives me a divider between commands that's easy to see when scrolling back. It also shows me the current directory without using horizontal space on the line that I'm typing on.
For example:
C:\Users\Jay ----------------------------------------------------------------------------------------------------------
[1] PS>
# ----------------------------------------------------------
# msdn search for win32 APIs.
# ----------------------------------------------------------
function Search-MSDNWin32
{
$url = 'http://search.msdn.microsoft.com/?query=';
$url += $args[0];
for ($i = 1; $i -lt $args.count; $i++) {
$url += '+';
$url += $args[$i];
}
$url += '&locale=en-us&refinement=86&ac=3';
Open-IE($url);
}
# ----------------------------------------------------------
# Open Internet Explorer given the url.
# ----------------------------------------------------------
function Open-IE ($url)
{
$ie = new-object -comobject internetexplorer.application;
$ie.Navigate($url);
$ie.Visible = $true;
}
I rock a few functions, and since I'm a module author I typically load a console and desperately need to know what's where.
write-host "Your modules are..." -ForegroundColor Red
Get-module -li
Die hard nerding:
function prompt
{
$host.UI.RawUI.WindowTitle = "ShellPower"
# Need to still show the working directory.
#Write-Host "You landed in $PWD"
# Nerd up, yo.
$Str = "Root#The Matrix"
"$str> "
}
The mandatory anything I can PowerShell I will functions go here...
# Explorer command
function Explore
{
param
(
[Parameter(
Position = 0,
ValueFromPipeline = $true,
Mandatory = $true,
HelpMessage = "This is the path to explore..."
)]
[ValidateNotNullOrEmpty()]
[string]
# First parameter is the path you're going to explore.
$Target
)
$exploration = New-Object -ComObject shell.application
$exploration.Explore($Target)
}
I am STILL an administrator so I do need...
Function RDP
{
param
(
[Parameter(
Position = 0,
ValueFromPipeline = $true,
Mandatory = $true,
HelpMessage = "Server Friendly name"
)]
[ValidateNotNullOrEmpty()]
[string]
$server
)
cmdkey /generic:TERMSRV/$server /user:$UserName /pass:($Password.GetNetworkCredential().Password)
mstsc /v:$Server /f /admin
Wait-Event -Timeout 5
cmdkey /Delete:TERMSRV/$server
}
Sometimes I want to start explorer as someone other than the logged in user...
# Restarts explorer as the user in $UserName
function New-Explorer
{
# CLI prompt for password
taskkill /f /IM Explorer.exe
runas /noprofile /netonly /user:$UserName explorer
}
This is just because it's funny.
Function Lock-RemoteWorkstation
{
param(
$Computername,
$Credential
)
if(!(get-module taskscheduler))
{
Import-Module TaskScheduler
}
New-task -ComputerName $Computername -credential:$Credential |
Add-TaskTrigger -In (New-TimeSpan -Seconds 30) |
Add-TaskAction -Script `
{
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
} | Register-ScheduledTask TestTask -ComputerName $Computername -credential:$Credential
}
I also have one for me, since Win + L is too far away...
Function llm # Lock Local machine
{
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
}
A few filters? I think so...
filter FileSizeBelow($size){if($_.length -le $size){ $_ }}
filter FileSizeAbove($size){if($_.Length -ge $size){$_}}
I also have a few I can't post yet, because they're not done but they're basically a way to persist credentials between sessions without writing them out as an encrypted file.
Here's my not so subtle profile
#==============================================================================
# Jared Parsons PowerShell Profile (jaredp#rantpack.org)
#==============================================================================
#==============================================================================
# Common Variables Start
#==============================================================================
$global:Jsh = new-object psobject
$Jsh | add-member NoteProperty "ScriptPath" $(split-path -parent $MyInvocation.MyCommand.Definition)
$Jsh | add-member NoteProperty "ConfigPath" $(split-path -parent $Jsh.ScriptPath)
$Jsh | add-member NoteProperty "UtilsRawPath" $(join-path $Jsh.ConfigPath "Utils")
$Jsh | add-member NoteProperty "UtilsPath" $(join-path $Jsh.UtilsRawPath $env:PROCESSOR_ARCHITECTURE)
$Jsh | add-member NoteProperty "GoMap" #{}
$Jsh | add-member NoteProperty "ScriptMap" #{}
#==============================================================================
#==============================================================================
# Functions
#==============================================================================
# Load snapin's if they are available
function Jsh.Load-Snapin([string]$name) {
$list = #( get-pssnapin | ? { $_.Name -eq $name })
if ( $list.Length -gt 0 ) {
return;
}
$snapin = get-pssnapin -registered | ? { $_.Name -eq $name }
if ( $snapin -ne $null ) {
add-pssnapin $name
}
}
# Update the configuration from the source code server
function Jsh.Update-WinConfig([bool]$force=$false) {
# First see if we've updated in the last day
$target = join-path $env:temp "Jsh.Update.txt"
$update = $false
if ( test-path $target ) {
$last = [datetime] (gc $target)
if ( ([DateTime]::Now - $last).Days -gt 1) {
$update = $true
}
} else {
$update = $true;
}
if ( $update -or $force ) {
write-host "Checking for winconfig updates"
pushd $Jsh.ConfigPath
$output = #(& svn update)
if ( $output.Length -gt 1 ) {
write-host "WinConfig updated. Re-running configuration"
cd $Jsh.ScriptPath
& .\ConfigureAll.ps1
. .\Profile.ps1
}
sc $target $([DateTime]::Now)
popd
}
}
function Jsh.Push-Path([string] $location) {
go $location $true
}
function Jsh.Go-Path([string] $location, [bool]$push = $false) {
if ( $location -eq "" ) {
write-output $Jsh.GoMap
} elseif ( $Jsh.GoMap.ContainsKey($location) ) {
if ( $push ) {
push-location $Jsh.GoMap[$location]
} else {
set-location $Jsh.GoMap[$location]
}
} elseif ( test-path $location ) {
if ( $push ) {
push-location $location
} else {
set-location $location
}
} else {
write-output "$loctaion is not a valid go location"
write-output "Current defined locations"
write-output $Jsh.GoMap
}
}
function Jsh.Run-Script([string] $name) {
if ( $Jsh.ScriptMap.ContainsKey($name) ) {
. $Jsh.ScriptMap[$name]
} else {
write-output "$name is not a valid script location"
write-output $Jsh.ScriptMap
}
}
# Set the prompt
function prompt() {
if ( Test-Admin ) {
write-host -NoNewLine -f red "Admin "
}
write-host -NoNewLine -ForegroundColor Green $(get-location)
foreach ( $entry in (get-location -stack)) {
write-host -NoNewLine -ForegroundColor Red '+';
}
write-host -NoNewLine -ForegroundColor Green '>'
' '
}
#==============================================================================
#==============================================================================
# Alias
#==============================================================================
set-alias gcid Get-ChildItemDirectory
set-alias wget Get-WebItem
set-alias ss select-string
set-alias ssr Select-StringRecurse
set-alias go Jsh.Go-Path
set-alias gop Jsh.Push-Path
set-alias script Jsh.Run-Script
set-alias ia Invoke-Admin
set-alias ica Invoke-CommandAdmin
set-alias isa Invoke-ScriptAdmin
#==============================================================================
pushd $Jsh.ScriptPath
# Setup the go locations
$Jsh.GoMap["ps"] = $Jsh.ScriptPath
$Jsh.GoMap["config"] = $Jsh.ConfigPath
$Jsh.GoMap["~"] = "~"
# Setup load locations
$Jsh.ScriptMap["profile"] = join-path $Jsh.ScriptPath "Profile.ps1"
$Jsh.ScriptMap["common"] = $(join-path $Jsh.ScriptPath "LibraryCommon.ps1")
$Jsh.ScriptMap["svn"] = $(join-path $Jsh.ScriptPath "LibrarySubversion.ps1")
$Jsh.ScriptMap["subversion"] = $(join-path $Jsh.ScriptPath "LibrarySubversion.ps1")
$Jsh.ScriptMap["favorites"] = $(join-path $Jsh.ScriptPath "LibraryFavorites.ps1")
$Jsh.ScriptMap["registry"] = $(join-path $Jsh.ScriptPath "LibraryRegistry.ps1")
$Jsh.ScriptMap["reg"] = $(join-path $Jsh.ScriptPath "LibraryRegistry.ps1")
$Jsh.ScriptMap["token"] = $(join-path $Jsh.ScriptPath "LibraryTokenize.ps1")
$Jsh.ScriptMap["unit"] = $(join-path $Jsh.ScriptPath "LibraryUnitTest.ps1")
$Jsh.ScriptMap["tfs"] = $(join-path $Jsh.ScriptPath "LibraryTfs.ps1")
$Jsh.ScriptMap["tab"] = $(join-path $Jsh.ScriptPath "TabExpansion.ps1")
# Load the common functions
. script common
. script tab
$global:libCommonCertPath = (join-path $Jsh.ConfigPath "Data\Certs\jaredp_code.pfx")
# Load the snapin's we want
Jsh.Load-Snapin "pscx"
Jsh.Load-Snapin "JshCmdlet"
# Setup the Console look and feel
$host.UI.RawUI.ForegroundColor = "Yellow"
if ( Test-Admin ) {
$title = "Administrator Shell - {0}" -f $host.UI.RawUI.WindowTitle
$host.UI.RawUI.WindowTitle = $title;
}
# Call the computer specific profile
$compProfile = join-path "Computers" ($env:ComputerName + "_Profile.ps1")
if ( -not (test-path $compProfile)) { ni $compProfile -type File | out-null }
write-host "Computer profile: $compProfile"
. ".\$compProfile"
$Jsh.ScriptMap["cprofile"] = resolve-path ($compProfile)
# If the computer name is the same as the domain then we are not
# joined to active directory
if ($env:UserDomain -ne $env:ComputerName ) {
# Call the domain specific profile data
write-host "Domain $env:UserDomain"
$domainProfile = join-path $env:UserDomain "Profile.ps1"
if ( -not (test-path $domainProfile)) { ni $domainProfile -type File | out-null }
. ".\$domainProfile"
}
# Run the get-fortune command if JshCmdlet was loaded
if ( get-command "get-fortune" -ea SilentlyContinue ) {
get-fortune -timeout 1000
}
# Finished with the profile, go back to the original directory
popd
# Look for updates
Jsh.Update-WinConfig
# Because this profile is run in the same context, we need to remove any
# variables manually that we don't want exposed outside this script
i add this function so that i can see disk usage easily:
function df {
$colItems = Get-wmiObject -class "Win32_LogicalDisk" -namespace "root\CIMV2" `
-computername localhost
foreach ($objItem in $colItems) {
write $objItem.DeviceID $objItem.Description $objItem.FileSystem `
($objItem.Size / 1GB).ToString("f3") ($objItem.FreeSpace / 1GB).ToString("f3")
}
}
apropos.
Although I think this has been superseded by a recent or upcoming release.
##############################################################################
## Search the PowerShell help documentation for a given keyword or regular
## expression.
##
## Example:
## Get-HelpMatch hashtable
## Get-HelpMatch "(datetime|ticks)"
##############################################################################
function apropos {
param($searchWord = $(throw "Please specify content to search for"))
$helpNames = $(get-help *)
foreach($helpTopic in $helpNames)
{
$content = get-help -Full $helpTopic.Name | out-string
if($content -match $searchWord)
{
$helpTopic | select Name,Synopsis
}
}
}
I keep a little bit of everything. Mostly, my profile sets up all the environment (including calling scripts to set up my .NET/VS and Java development environment).
I also redefine the prompt() function with my own style (see it in action), set up several aliases to other scripts and commands. and change what $HOME points to.
Here's my complete profile script.
Set-PSDebug -Strict
You will benefit i you ever searched for a stupid Typo eg. outputting $varsometext instead $var sometext
##############################################################################
# Get an XPath Navigator object based on the input string containing xml
function get-xpn ($text) {
$rdr = [System.IO.StringReader] $text
$trdr = [system.io.textreader]$rdr
$xpdoc = [System.XML.XPath.XPathDocument] $trdr
$xpdoc.CreateNavigator()
}
Useful for working with xml, such as output from svn commands with --xml.
This creates a scripts: drive and adds it to your path. Note, you must create the folder yourself. Next time you need to get back to it, just type "scripts:" and hit enter, just like any drive letter in Windows.
$env:path += ";$profiledir\scripts"
New-PSDrive -Name Scripts -PSProvider FileSystem -Root $profiledir\scripts
This will add snapins you have installed into your powershell session. The reason you may want to do something like this is that it's easy to maintain, and works well if you sync your profile across multiple systems. If a snapin isn't installed, you won't see an error message.
---------------------------------------------------------------------------
Add third-party snapins
---------------------------------------------------------------------------
$snapins = #(
"Quest.ActiveRoles.ADManagement",
"PowerGadgets",
"VMware.VimAutomation.Core",
"NetCmdlets"
)
$snapins | ForEach-Object {
if ( Get-PSSnapin -Registered $_ -ErrorAction SilentlyContinue ) {
Add-PSSnapin $_
}
}
I put all my functions and aliases in separate script files and then dot source them in my profile:
. c:\scripts\posh\jdh-functions.ps1
The function to view the entire history of typed command (Get-History, and his alias h show default only 32 last commands):
function ha {
Get-History -count $MaximumHistoryCount
}
You can see my PowerShell profile at http://github.com/jamesottaway/windowspowershell
If you use Git to clone my repo into your Documents folder (or whatever folder is above 'WindowsPowerShell' in your $PROFILE variable), you'll get all of my goodness.
The main profile.ps1 sets the subfolder with the name Addons as a PSDrive, and then finds all .ps1 files underneath that folder to load.
I quite like the go command, which stores a dictionary of shorthand locations to visit easily. For example, go vsp will take me to C:\Visual Studio 2008\Projects.
I also like overriding the Set-Location cmdlet to run both Set-Location and Get-ChildItem.
My other favourite is being able to do a mkdir which does Set-Location xyz after running New-Item xyz -Type Directory.
amongst many other things:
function w {
explorer .
}
opens an explorer window in the current directory
function startover {
iisreset /restart
iisreset /stop
rm "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\*.*" -recurse -force -Verbose
iisreset /start
}
gets rid of everything in my temporary asp.net files (useful for working on managed code that has dependencies on buggy unmanaged code)
function edit($x) {
. 'C:\Program Files (x86)\Notepad++\notepad++.exe' $x
}
edits $x in notepad++
I actually keep mine on github.
Function funcOpenPowerShellProfile
{
Notepad $PROFILE
}
Set-Alias fop funcOpenPowerShellProfile
Only a sagaciously-lazy individual would tell you that fop is so much easier to type than Notepad $PROFILE at the prompt, unless, of course, you associate "fop" with a 17th century English ninny.
If you wanted, you could take it a step further and make it somewhat useful:
Function funcOpenPowerShellProfile
{
$fileProfileBackup = $PROFILE + '.bak'
cp $PROFILE $fileProfileBackup
PowerShell_ISE $PROFILE # Replace with Desired IDE/ISE for Syntax Highlighting
}
Set-Alias fop funcOpenPowerShellProfile
For satisfying survivalist-paranoia:
Function funcOpenPowerShellProfile
{
$fileProfilePathParts = #($PROFILE.Split('\'))
$fileProfileName = $fileProfilePathParts[-1]
$fileProfilePathPartNum = 0
$fileProfileHostPath = $fileProfilePathParts[$fileProfilePathPartNum] + '\'
$fileProfileHostPathPartsCount = $fileProfilePathParts.Count - 2
# Arrays start at 0, but the Count starts at 1; if both started at 0 or 1,
# then a -1 would be fine, but the realized discrepancy is 2
Do
{
$fileProfilePathPartNum++
$fileProfileHostPath = $fileProfileHostPath + `
$fileProfilePathParts[$fileProfilePathPartNum] + '\'
}
While
(
$fileProfilePathPartNum -LT $fileProfileHostPathPartsCount
)
$fileProfileBackupTime = [string](date -format u) -replace ":", ""
$fileProfileBackup = $fileProfileHostPath + `
$fileProfileBackupTime + ' - ' + $fileProfileName + '.bak'
cp $PROFILE $fileProfileBackup
cd $fileProfileHostPath
$fileProfileBackupNamePattern = $fileProfileName + '.bak'
$fileProfileBackups = #(ls | Where {$_.Name -Match $fileProfileBackupNamePattern} | `
Sort Name)
$fileProfileBackupsCount = $fileProfileBackups.Count
$fileProfileBackupThreshold = 5 # Change as Desired
If
(
$fileProfileBackupsCount -GT $fileProfileBackupThreshold
)
{
$fileProfileBackupsDeleteNum = $fileProfileBackupsCount - `
$fileProfileBackupThreshold
$fileProfileBackupsIndexNum = 0
Do
{
rm $fileProfileBackups[$fileProfileBackupsIndexNum]
$fileProfileBackupsIndexNum++;
$fileProfileBackupsDeleteNum--
}
While
(
$fileProfileBackupsDeleteNum -NE 0
)
}
PowerShell_ISE $PROFILE
# Replace 'PowerShell_ISE' with Desired IDE (IDE's path may be needed in
# '$Env:PATH' for this to work; if you can start it from the "Run" window,
# you should be fine)
}
Set-Alias fop funcOpenPowerShellProfile
Jeffrey Snover's Start-NewScope because re-launching the shell can be a drag.
I never got comfortable with the diruse options, so:
function Get-FolderSizes { # poor man's du
[cmdletBinding()]
param(
[parameter(mandatory=$true)]$Path,
[parameter(mandatory=$false)]$SizeMB,
[parameter(mandatory=$false)]$ExcludeFolders,
[parameter(mandatory=$false)][switch]$AsObject
) #close param
# http://blogs.technet.com/b/heyscriptingguy/archive/2013/01/05/weekend-scripter-sorting-folders-by-size.aspx
# uses Christoph Schneegans' Find-Files https://schneegans.de/windows/find-files/ because "gci -rec" follows junctions in "special" folders
$pathCheck = test-path $path
if (!$pathcheck) { Write-Error "Invalid path. Wants gci's -path parameter."; return }
if (!(Get-Command Find-Files)) { Write-Error "Required function Find-Files not found"; return }
$fso = New-Object -ComObject scripting.filesystemobject
$parents = Get-ChildItem $path -Force | where { $_.PSisContainer -and $ExcludeFolders -notContains $_.name -and !$_.LinkType }
$folders = Foreach ($folder in $parents)
{
$getFolder = $fso.getFolder( $folder.fullname.tostring() )
if (!$getFolder.Size)
{
#for "special folders" like appdata
# maybe "-Attributes !ReparsePoint" works in v6? https://stackoverflow.com/a/59952913/
# what about https://superuser.com/a/650476/ ?
# abandoned because it follows junctions, distorting results # $length = gci $folder.FullName -Recurse -Force -EA SilentlyContinue | Measure -Property Length -Sum
$length = Find-Files $folder.FullName -EA SilentlyContinue | Measure -Property Length -Sum -EA SilentlyContinue
$sizeMBs = "{0:N0}" -f ($length.Sum /1mb)
} #close if size property is null
else { $sizeMBs = "{0:N0}" -f ($getFolder.size /1mb) }
New-Object -TypeName psobject -Property #{
Name = $getFolder.Path
SizeMB = $sizeMBs
} #close new obj property
} #close foreach folder
#here's the output
$foldersObj = $folders | Sort #{E={[decimal]$_.SizeMB}} -Descending | ? {[Decimal]$_.SizeMB -gt $SizeMB}
if (!$AsObject) { $foldersObj | Format-Table -AutoSize } else { $foldersObj }
#calculate the total including contents
$sum = $folders | Select -Expand SizeMB | Measure -Sum | Select -Expand Sum
$sum += ( gci $path | where {!$_.psIsContainer} | Measure -Property Length -Sum | Select -Expand Sum ) / 1mb
$sumString = "{0:n2}" -f ($sum /1kb)
$sumString + " GB total"
} #end function
Set-Alias gfs Get-FolderSizes
function Find-Files
{
<# by Christoph Schneegans https://schneegans.de/windows/find-files/ - used in Get-FolderSizes aka gfs
.SYNOPSIS
Lists the contents of a directory. Unlike Get-ChildItem, this function does not recurse into symbolic links or junctions in order to avoid infinite loops.
#>
param (
[Parameter( Mandatory=$false )]
[string]
# Specifies the path to the directory whose contents are to be listed. By default, the current working directory is used.
$LiteralPath = (Get-Location),
[Parameter( Mandatory=$false )]
# Specifies a filter that is applied to each file or directory. Wildcards ? and * are supported.
$Filter,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if file objects should be returned. By default, all file system objects are returned.
$File = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if directory objects should be returned. By default, all file system objects are returned.
$Directory = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if reparse point objects should be returned. By default, all file system objects are returned.
$ReparsePoint = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if the top directory should be returned. By default, all file system objects are returned.
$Self = $true
)
function Enumerate( [System.IO.FileSystemInfo] $Item ) {
$Item;
if ( $Item.GetType() -eq [System.IO.DirectoryInfo] -and ! $Item.Attributes.HasFlag( [System.IO.FileAttributes]::ReparsePoint ) ) {
foreach ($ChildItem in $Item.EnumerateFileSystemInfos() ) {
Enumerate $ChildItem;
}
}
}
function FilterByName {
process {
if ( ( $Filter -eq $null ) -or ( $_.Name -ilike $Filter ) ) {
$_;
}
}
}
function FilterByType {
process {
if ( $_.GetType() -eq [System.IO.FileInfo] ) {
if ( $File ) { $_; }
} elseif ( $_.Attributes.HasFlag( [System.IO.FileAttributes]::ReparsePoint ) ) {
if ( $ReparsePoint ) { $_; }
} else {
if ( $Directory ) { $_; }
}
}
}
$Skip = if ($Self) { 0 } else { 1 };
Enumerate ( Get-Item -LiteralPath $LiteralPath -Force ) | Select-Object -Skip $Skip | FilterByName | FilterByType;
} # end function find-files
The most valuable bit above is Christoph Schneegans' Find-Files https://schneegans.de/windows/find-files
For pointing at stuff:
function New-URLfile {
param( [parameter(mandatory=$true)]$Target, [parameter(mandatory=$true)]$Link )
if ($target -match "^\." -or $link -match "^\.") {"Full paths plz."; break}
$content = #()
$header = '[InternetShortcut]'
$content += $header
$content += "URL=" + $target
$content | out-file $link
ii $link
} #end function
function New-LNKFile {
param( [parameter(mandatory=$true)]$Target, [parameter(mandatory=$true)]$Link )
if ($target -match "^\." -or $link -match "^\.") {"Full paths plz."; break}
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($link)
$Shortcut.TargetPath = $target
$shortCut.save()
} #end function new-lnkfile
Poor man's grep? For searching large txt files.
function Search-TextFile {
param(
[parameter(mandatory=$true)]$File,
[parameter(mandatory=$true)]$SearchText
) #close param
if ( !(Test-path $File) )
{
Write-Error "File not found: $file"
return
}
$fullPath = Resolve-Path $file | select -Expand ProviderPath
$lines = [System.IO.File]::ReadLines($fullPath)
foreach ($line in $lines) { if ($line -match $SearchText) {$line} }
} #end function Search-TextFile
Set-Alias stf Search-TextFile
Lists programs installed on a remote computer.
function Get-InstalledProgram { [cmdletBinding()] #http://blogs.technet.com/b/heyscriptingguy/archive/2011/11/13/use-powershell-to-quickly-find-installed-software.aspx
param( [parameter(mandatory=$true)]$Comp,[parameter(mandatory=$false)]$Name )
$keys = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
TRY { $RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Comp) }
CATCH {
$rrSvc = gwmi win32_service -comp $comp -Filter {name='RemoteRegistry'}
if (!$rrSvc) {"Unable to connect. Make sure that this computer is on the network, has remote administration enabled, `nand that both computers are running the remote registry service."; break}
#Enable and start RemoteRegistry service
if ($rrSvc.State -ne 'Running') {
if ($rrSvc.StartMode -eq 'Disabled') { $null = $rrSvc.ChangeStartMode('Manual'); $undoMe2 = $true }
$null = $rrSvc.StartService() ; $undoMe = $true
} #close if rrsvc not running
else {"Unable to connect. Make sure that this computer is on the network, has remote administration enabled, `nand that both computers are running the remote registry service."; break}
$RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Comp)
} #close if failed to connect regbase
$out = #()
foreach ($key in $keys) {
if ( $RegBase.OpenSubKey($Key) ) { #avoids errors on 32bit OS
foreach ( $entry in $RegBase.OpenSubKey($Key).GetSubkeyNames() ) {
$sub = $RegBase.OpenSubKey( ($key + '\' + $entry) )
if ($sub) { $row = $null
$row = [pscustomobject]#{
Name = $RegBase.OpenSubKey( ($key + '\' + $entry) ).GetValue('DisplayName')
InstallDate = $RegBase.OpenSubKey( ($key + '\' + $entry) ).GetValue('InstallDate')
Version = $RegBase.OpenSubKey( ($key + '\' + $entry) ).GetValue('DisplayVersion')
} #close row
$out += $row
} #close if sub
} #close foreach entry
} #close if key exists
} #close foreach key
$out | where {$_.name -and $_.name -match $Name}
if ($undoMe) { $null = $rrSvc.StopService() }
if ($undoMe2) { $null = $rrSvc.ChangeStartMode('Disabled') }
} #end function
Going meta, spreading the gospel, whatnot
function Copy-ProfilePS1 ($Comp,$User) {
if (!$User) {$User = $env:USERNAME}
$targ = "\\$comp\c$\users\$User\Documents\WindowsPowershell\"
if (Test-Path $targ)
{
$cmd = "copy /-Y $profile $targ"
cmd /c $cmd
} else {"Path not found! $targ"}
} #end function CopyProfilePS1
$MaximumHistoryCount=1024
function hist {get-history -count 256 | %{$_.commandline}}
New-Alias which get-command
function guidConverter([byte[]] $gross){ $GUID = "{" + $gross[3].ToString("X2") + `
$gross[2].ToString("X2") + $gross[1].ToString("X2") + $gross[0].ToString("X2") + "-" + `
$gross[5].ToString("X2") + $gross[4].ToString("X2") + "-" + $gross[7].ToString("X2") + `
$gross[6].ToString("X2") + "-" + $gross[8].ToString("X2") + $gross[9].ToString("X2") + "-" +`
$gross[10].ToString("X2") + $gross[11].ToString("X2") + $gross[12].ToString("X2") + `
$gross[13].ToString("X2") + $gross[14].ToString("X2") + $gross[15].ToString("X2") + "}" $GUID }
I keep my profile empty. Instead, I have folders of scripts I can navigate to load functionality and aliases into the session. A folder will be modular, with libraries of functions and assemblies. For ad hoc work, I'll have a script to loads aliases and functions. If I want to munge event logs, I'd navigate to a folder scripts\eventlogs and execute
PS > . .\DotSourceThisToLoadSomeHandyEventLogMonitoringFunctions.ps1
I do this because I need to share scripts with others or move them from machine to machine. I like to be able to copy a folder of scripts and assemblies and have it just work on any machine for any user.
But you want a fun collection of tricks. Here's a script that many of my "profiles" depend on. It allows calls to web services that use self signed SSL for ad hoc exploration of web services in development. Yes, I freely mix C# in my powershell scripts.
# Using a target web service that requires SSL, but server is self-signed.
# Without this, we'll fail unable to establish trust relationship.
function Set-CertificateValidationCallback
{
try
{
Add-Type #'
using System;
public static class CertificateAcceptor{
public static void SetAccept()
{
System.Net.ServicePointManager.ServerCertificateValidationCallback = AcceptCertificate;
}
private static bool AcceptCertificate(Object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors policyErrors)
{
Console.WriteLine("Accepting certificate and ignoring any SSL errors.");
return true;
}
}
'#
}
catch {} # Already exists? Find a better way to check.
[CertificateAcceptor]::SetAccept()
}
Great question. Because I deal with several different PowerShell hosts, I do a little logging in each of several profiles, just to make the context of any other messages clearer. In profile.ps1, I currently only have that, but I sometimes change it based on context:
if ($PSVersionTable.PsVersion.Major -ge 3) {
Write-Host "Executing $PSCommandPath"
}
My favorite host is the ISE, in Microsoft.PowerShellIse_profile.ps1, I have:
if ($PSVersionTable.PsVersion.Major -ge 3) {
Write-Host "Executing $PSCommandPath"
}
if ( New-PSDrive -ErrorAction Ignore One FileSystem `
(Get-ItemProperty hkcu:\Software\Microsoft\SkyDrive UserFolder).UserFolder) {
Write-Host -ForegroundColor Green "PSDrive One: mapped to local OneDrive/SkyDrive folder"
}
Import-Module PSCX
$PSCX:TextEditor = (get-command Powershell_ISE).Path
$PSDefaultParameterValues = #{
"Get-Help:ShowWindow" = $true
"Help:ShowWindow" = $true
"Out-Default:OutVariable" = "0"
}
#Script Browser Begin
#Version: 1.2.1
Add-Type -Path 'C:\Program Files (x86)\Microsoft Corporation\Microsoft Script Browser\System.Windows.Interactivity.dll'
Add-Type -Path 'C:\Program Files (x86)\Microsoft Corporation\Microsoft Script Browser\ScriptBrowser.dll'
Add-Type -Path 'C:\Program Files (x86)\Microsoft Corporation\Microsoft Script Browser\BestPractices.dll'
$scriptBrowser = $psISE.CurrentPowerShellTab.VerticalAddOnTools.Add('Script Browser', [ScriptExplorer.Views.MainView], $true)
$scriptAnalyzer = $psISE.CurrentPowerShellTab.VerticalAddOnTools.Add('Script Analyzer', [BestPractices.Views.BestPracticesView], $true)
$psISE.CurrentPowerShellTab.VisibleVerticalAddOnTools.SelectedAddOnTool = $scriptBrowser
#Script Browser End
Of everything not already listed, Start-Steroids has to be my favorite, except for maybe Start-Transcript.
(http://www.powertheshell.com/isesteroids2-2/)