How can I make a StreamReader Open/Read/Close process into a function - powershell

In Powershell, I frequently use a StreamReader to iterate over files and read/manipulate text. Instead of constantly having to type a script similar to:
## StreamReader setup / file availability check
try {
## Create Stream Reader
$readStream = [System.IO.StreamReader]::new($Path)
## Do stuff ...
} finally {
$readStream.close()
}
How can I make the entire setup/open/close process into a function that I can call whenever I need to automate the 'Do Stuff' portion of my above code? I am aware of how to make functions but I cant figure out how to turn this into a usable function so I only have to write it and edit it once but can use many times.

This may not be the most elegant solution but it does work.
You have different Functions for each type of processing I just called my test Process-Stream.
Function Process-Stream {
Do {
$Line = $readStream.ReadLine()
"$Line"
} While ($readStream.EndOfStream -eq $False)
} #End Function Process-Stream
Next you have a function that does all of your setup and error processing for the Stream.
Function Get-Stream {
Param (
[Parameter(Mandatory=$True)]
[String] $SourcePath,
[Parameter(Mandatory=$True)]
[String] $ProcessFunction
)
try {
## Create Stream Reader
$readStream = [System.IO.StreamReader]::new(
"$SourcePath")
& $ProcessFunction
} finally {
$readStream.close()
}
} #End Function Get-Stream
Now you just call Get-Stream with the name of your processing function.
PS> Get-Stream -SourcePath "G:\Test\StreamIOTest.txt" -ProcessFunction Process-Stream
Line 1
Line 2
Line 3
Line 4
PS>
Note: the test text file I used had 4 lines. Don't forget you need to have the functions loaded!
Updated: I realized after I posted that I should have parameterized the file to be read and passed that into Get-Stream also.
HTH

Assuming that you want to process your text files line by line:
There's no need to deal with [System.IO.StreamReader] instances directly - you can use PowerShell's built-in features.
In the simplest case, if performance isn't paramount, combine Get-Content with ForEach-Object:
Get-Content $Path | ForEach-Object { <# Do Stuff with $_, the line at hand #> }
When performance matters, use a switch statement with the -File parameter:
switch -File $Path { Default { <# Do Stuff with $_, the line at hand #> } }

Related

Bulk-Assign PowerShell Variables using Multi-Level [PSCustomObject]

TLDR
I'm trying to create a function that will take a Multi-Level [PSCustomObject], extract the Key/Value pairs (strings only), and use them to declare Individual Global Variables using Set-Variable.
Current Code
Set-Variable -Name 'NSOneDrive' -Value "D:\OneDrive - New Spectrum"
$StrykerDirs = [PSCustomObject]#{
'OneDrive' = [PSCustomObject]#{
'NSOneDrive' = "D:\OneDrive - New Spectrum"
'MyOneDrive' = "D:\OneDrive"
}
'Dev' = [PSCustomObject]#{
'DevDir' = "${NSOneDrive}\Dev"
'DevToolsDir' = [PSCustomObject]#{
'DevTools' = "${NSOneDrive}\Dev\_DevTools"
'Terminals' = [PSCustomObject]#{
'DT_Terminals' = "${NSOneDrive}\Dev\_DevTools\terminals"
'DT_PowerShell' = "${NSOneDrive}\Dev\_DevTools\terminals\PowerShell"
}
'Editors' = [PSCustomObject]#{
'DT_Editors' = "${NSOneDrive}\Dev\_DevTools\.editors"
}
}
'ProjectsDir' = [PSCustomObject]#{
'NSProjects' = "${NSOneDrive}\Projects\NewSpectrum"
'MyProjects' = "${NSOneDrive}\Projects\Personal"
}
}
}
$StrykerDirs |
ConvertTo-JSON -Depth 25 |
Tee-Object -FilePath ".\JSON\Stryker-Paths.json"
function Set-DirAliases {
[CmdletBinding()]
# I might add parameters after I know how to make the 'Process' work
Begin {
# Begin Process Block
}
Process {
ForEach ( $dir in $StrykerDirs ) {
where ( $_.GetType() -eq 'String' ) |
Set-Variable -Name "${key}" -Value "${value}"
# I know ${key} and ${value} won't work, but I'm not sure how to properly fill them
}
}
End {
# End Process Block
}
}
Goals
Simplifying Set-Location Navigation
First and foremost I obviously need to figure out how to make the above Process block work. Once I do, I'll be able to easily declare Directory Variables for use with Set-Location. This is only for streamlining variable declarations so I don't have to repeatedly declare them with a messy barrage of individual Set-Variable commands while also avoiding the use of long (sometimes very long) $Object.PropertyName 'variables'.
After I get a handle on this script, I'll be able to finish several other scripts and functions that use (more or less) the same basic process.
Add to $PROFILE
This particular script is going to be part of a 'Startups' section in my default $PROFILE (Microsoft.PowerShell_profile.ps1) so I can set the Directory Variables in-bulk and keep the $PROFILE script itself nice and clean.
The other scripts that I mentioned anbove are also going to be included in my $PROFILE Startups.
JSON Output
The script also exports a .json file so that, among other things, I can (hopefully) repeat the process down the road in my WSL Bash Profiles.
Param() Functionality
Eventually I want to add a Param() block so the function can be used outside of the script as well.

Using Pipeline to pass an object to another Powershell script [duplicate]

I am trying to write a PowerShell script that can get pipeline input (and is expected to do so), but trying something like
ForEach-Object {
# do something
}
doesn't actually work when using the script from the commandline as follows:
1..20 | .\test.ps1
Is there a way?
Note: I know about functions and filters. This is not what I am looking for.
In v2 you can also accept pipeline input (by propertyName or byValue), add parameter aliases etc:
function Get-File{
param(
[Parameter(
Position=0,
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)
]
[Alias('FullName')]
[String[]]$FilePath
)
process {
foreach($path in $FilePath)
{
Write-Host "file path is: $path"
}
}
}
# test ValueFromPipelineByPropertyName
dir | Get-File
# test ValueFromPipeline (byValue)
"D:\scripts\s1.txt","D:\scripts\s2.txt" | Get-File
- or -
dir *.txt | foreach {$_.fullname} | Get-File
This works and there are probably other ways to do it:
foreach ($i in $input) {
$i
}
17:12:42 PS>1..20 | .\cmd-input.ps1
1
2
3
-- snip --
18
19
20
Search for "powershell $input variable" and you will find more information and examples.
A couple are here:
PowerShell Functions and Filters PowerShell Pro!
(see the section on "Using the PowerShell Special Variable “$input”")
"Scripts, functions, and script blocks all have access to the $input variable, which provides an enumerator over the elements in the incoming pipeline. "
or
$input gotchas « Dmitry’s PowerBlog PowerShell and beyond
"... basically $input in an enumerator which provides access to the pipeline you have."
For the PS command line, not the DOS command line Windows Command Processor.
You can either write a filter which is a special case of a function like so:
filter SquareIt([int]$num) { $_ * $_ }
or you can create a similar function like so:
function SquareIt([int]$num) {
Begin {
# Executes once before first item in pipeline is processed
}
Process {
# Executes once for each pipeline object
$_ * $_
}
End {
# Executes once after last pipeline object is processed
}
}
The above works as an interactive function definiton or if in a script can be dotted into your global session (or another script). However your example indicated you wanted a script so here it is in a script that is directly usable (no dotting required):
--- Contents of test.ps1 ---
param([int]$num)
Begin {
# Executes once before first item in pipeline is processed
}
Process {
# Executes once for each pipeline object
$_ * $_
}
End {
# Executes once after last pipeline object is processed
}
With PowerShell V2, this changes a bit with "advanced functions" which embue functions with the same parameter binding features that compiled cmdlets have. See this blog post for an example of the differences. Also note that in this advanced functions case you don't use $_ to access the pipeline object. With advanced functions, pipeline objects get bound to a parameter just like they do with a cmdlet.
The following are the simplest possible examples of scripts/functions that use piped input. Each behaves the same as piping to the "echo" cmdlet.
As Scripts:
# Echo-Pipe.ps1
Begin {
# Executes once before first item in pipeline is processed
}
Process {
# Executes once for each pipeline object
echo $_
}
End {
# Executes once after last pipeline object is processed
}
# Echo-Pipe2.ps1
foreach ($i in $input) {
$i
}
As functions:
Function Echo-Pipe {
Begin {
# Executes once before first item in pipeline is processed
}
Process {
# Executes once for each pipeline object
echo $_
}
End {
# Executes once after last pipeline object is processed
}
}
Function Echo-Pipe2 {
foreach ($i in $input) {
$i
}
}
E.g.
PS > . theFileThatContainsTheFunctions.ps1 # This includes the functions into your session
PS > echo "hello world" | Echo-Pipe
hello world
PS > cat aFileWithThreeTestLines.txt | Echo-Pipe2
The first test line
The second test line
The third test line

How to restore language settings in Windows from a saved file using a powershell script?

I need to create a PowerShell script which will restore language settings and input methods (important) from a file. At first I thought it would be easy to use Get-WinUserLanguageList | Export-CliXML ./mylist.xml to save current settings and then $List = Import-CliXML ./mylist.xml, Set-WinUserLanguageList -LanguageList $List, however it does not work because the data imported from XML into the variable is deserialized and I get an exception:
Set-WinUserLanguageList : Cannot bind parameter 'LanguageList'. Cannot convert the "Microsoft.InternationalSettings.Commands.WinUserLanguage" value of type
"Deserialized.Microsoft.InternationalSettings.Commands.WinUserLanguage" to type
"Microsoft.InternationalSettings.Commands.WinUserLanguage".
I've tried using XML but failed, so I created a workaround which looks like this:
[CmdletBinding()]
param (
[switch]$GenerateList
)
function Generate-List { # Generates language files to restore from.
$GoodList = Get-WinUserLanguageList
[string[]]$LanguageTags = $GoodList.LanguageTag
$LanguageTags | Out-File .\LanguageTags.txt
[string[]]$InputMethods = $GoodList.InputMethodTips
$InputMethods | Out-File .\InputMethods.txt
} # Exporting languages and corresponding input methods in separate files. Can be improved.
if ($GenerateList -eq $true) {
Generate-List
} # Invokes a function based on a switch parameter.
function RestoreFrom-List {
$GoodList = Get-WinUserLanguageList # Make our variable of a proper type
$GoodList.Clear() # Clear the variable contents
[string[]]$LanguageTags = Get-Content .\LanguageTags.txt
[string[]]$InputMethods = Get-Content .\InputMethods.txt
foreach ($language in $LanguageTags) { # This loop fills $GoodList with proper values
$index = $LanguageTags.IndexOf($language)
$GoodList.Add($language) # Add a language to the list
$GoodList[$index].InputMethodTips.Clear() # Remove default input method
$GoodList[$index].InputMethodTips.Add($InputMethods[$index]) # Add an input method from a corresponding position in the saved txt file
}
Set-WinUserLanguageList $GoodList -force # Restore system languages and input methods using a freshly created list
}
RestoreFrom-List
I am new to PowerShell and I am sure this code is ugly and can be improved. Also, it feels that *-WinUserLanguageList cmdlets are a pain to work with - you have to use internal methods to change data instead of the universal Set-Property.
So far my script successfully exports language/input-method settings into two(!) txt(!!) files and doesn't work if a particular language has two or more input methods (because it dumps input methods into an array without relating them to a particular language and then retrieves them based on an index). Please help improving this.
Decided to revisit my old question and post an improved version of the script which uses JSON to store exported languages. Any number of languages and input methods is supported now, and they are stored as a properly structured JSON file. Handwriting and spellchecking settings are also preserved.
# Exports/imports windows language settings to/from a file
function ImportExport-Languages {
[CmdletBinding()]
param (
[switch]$GenerateList,
[string]$Path
)
## Export
function Export-Lang {
Get-WinUserLanguageList | ConvertTo-Json | Out-File $Path
}
## Import
function Import-Lang {
$importedFile = Get-Content $Path | ConvertFrom-Json
$langCollection = New-Object Microsoft.InternationalSettings.Commands.WinUserLanguage[] ""
foreach ($item in $importedFile)
{
$lang = [Microsoft.InternationalSettings.Commands.WinUserLanguage]::new($item.LanguageTag)
$lang.InputMethodTips.Clear() # clear default auto-generated input method
foreach ($inputMethod in $item.InputMethodTips)
{
$lang.InputMethodTips.Add($inputMethod)
}
$lang.Handwriting = $item.Handwriting
$lang.Spellchecking = $item.Spellchecking
$langCollection += $lang
}
Set-WinUserLanguageList $langCollection -Force
}
## Run
if ($GenerateList) { Export-Lang }
Import-Lang
}
ImportExport-Languages -Path "C:\MyFolder\MyFile.json" -GenerateList
Hi I modified it a little bit because it was not working on windows 10 pro. Changed the array to list of languages now works like a charm.
function ImportExport-Languages {
[CmdletBinding()]
param (
[switch]$GenerateList,
[string]$Path
)
## Export
function Export-Lang {
Get-WinUserLanguageList | ConvertTo-Json | Out-File $Path
}
## Import
function Import-Lang {
$importedFile = Get-Content $Path | ConvertFrom-Json
$langCollection = New-Object System.Collections.Generic.List[Microsoft.InternationalSettings.Commands.WinUserLanguage]
foreach ($item in $importedFile)
{
$lang = [Microsoft.InternationalSettings.Commands.WinUserLanguage]::new($item.LanguageTag)
$lang.InputMethodTips.Clear() # clear default auto-generated input method
foreach ($inputMethod in $item.InputMethodTips)
{
$lang.InputMethodTips.Add($inputMethod)
}
$lang.Handwriting = $item.Handwriting
$lang.Spellchecking = $item.Spellchecking
$langCollection.Add($lang)
}
Set-WinUserLanguageList $langCollection -Force
}
## Run
if ($GenerateList) { Export-Lang }
Import-Lang
}
# usage export ImportExport-Languages -Path "C:\Install\languageExport.json" -GenerateList
# usage import ImportExport-Languages -Path "C:\Install\languageExport.json"

Is there a PowerShell equivalent for Python's doctest module?

I've just come across the doctest module in Python, which helps you perform automated testing against example code that's embedded within Python doc-strings. This ultimately helps ensure consistency between the documentation for a Python module, and the module's actual behavior.
Is there an equivalent capability in PowerShell, so I can test examples in the .EXAMPLE sections of PowerShell's built-in help?
This is an example of what I would be trying to do:
function MyFunction ($x, $y) {
<#
.EXAMPLE
> MyFunction -x 2 -y 2
4
#>
return $x + $y
}
MyFunction -x 2 -y 2
You could do this, although I'm not aware of any all-in-one built-in way to do it.
Method 1 - Create a scriptblock and execute it
Help documentation is an object, so can be leveraged to index into examples and their code. Below is the simplest example I could think of which executes your example code.
I'm not sure if this is what doctest does - it seems a bit dangerous to me but it might be what you're after! It's the simplest solution and I think will give you the most accurate results.
Function Test-Example {
param (
$Module
)
# Get the examples
$examples = Get-Help $Module -Examples
# Loop over the code of each example
foreach ($exampleCode in $examples.examples.example.code) {
# create a scriptblock of your code
$scriptBlock = [scriptblock]::Create($exampleCode)
# execute the scriptblock
$scriptBlock.Invoke()
}
}
Method 2 - Parse the example/function and make manual assertions
I think a potentially better way to this would be to parse your example and parse the function to make sure it's valid. The downside is this can get quite complex, especially if you're writing complex functions.
Here's some code that checks the example has the correct function name, parameters and valid values. It could probably be refactored (first time dealing with [System.Management.Automation.Language.Parser]) and doesn't deal with advanced functions at all.
If you care about things like Mandatory, ParameterSetName, ValidatePattern etc this probably isn't a good solution as it will require a lot of extension.
Function Check-Example {
param (
$Function
)
# we'll use this to get the example command later
# source: https://vexx32.github.io/2018/12/20/Searching-PowerShell-Abstract-Syntax-Tree/
$commandAstPredicate = {
param([System.Management.Automation.Language.Ast]$AstObject)
return ($AstObject -is [System.Management.Automation.Language.CommandAst])
}
# Get the examples
$examples = Get-Help $Function -Examples
# Parse the function
$parsedFunction = [System.Management.Automation.Language.Parser]::ParseInput((Get-Content Function:$Function), [ref]$null, [ref]$null)
# Loop over the code of each example
foreach ($exampleCode in $examples.examples.example.code) {
# parse the example code
$parsedExample = [System.Management.Automation.Language.Parser]::ParseInput($exampleCode, [ref]$null, [ref]$null)
# get the command, which gives us useful properties we can use
$parsedExampleCommand = $parsedExample.Find($commandAstPredicate,$true).CommandElements
# check the command name is correct
"Function is correctly named: $($parsedExampleCommand[0].Value -eq $Function)"
# loop over the command elements. skip the first one, which we assume is the function name
foreach ($element in ($parsedExampleCommand | select -Skip 1)) {
"" # new line
# check parameter in example exists in function definition
if ($element.ParameterName) {
"Checking parameter $($element.ParameterName)"
$parameterDefinition = $parsedFunction.ParamBlock.Parameters | where {$_.Name.VariablePath.Userpath -eq $element.ParameterName}
if ($parameterDefinition) {
"Parameter $($element.ParameterName) exists"
# store the parameter name so we can use it to check the value, which we should find in the next loop
# this falls apart for switches, which have no value so they'll need some additional logic
$previousParameterName = $element.ParameterName
}
}
# check the value has the same type as defined in the function, or can at least be cast to it.
elseif ($element.Value) {
"Checking value $($element.Value) of parameter $previousParameterName"
$parameterDefinition = $parsedFunction.ParamBlock.Parameters | where {$_.Name.VariablePath.Userpath -eq $previousParameterName}
"Parameter $previousParameterName has the same type: $($element.StaticType.Name -eq $parameterDefinition.StaticType.Name)"
"Parameter $previousParameterName can be cast to correct type: $(-not [string]::IsNullOrEmpty($element.Value -as $parameterDefinition.StaticType))"
}
else {
"Unexpected command element:"
$element
}
}
}
}
Method 3 - Use Pester (maybe out of scope)
I think this one is a bit off topic, but worth mentioning. Pester is the test framework for PowerShell and has features that could be helpful here. You could have a generic test that takes a script/function as argument and runs tests against the parsed examples/functions.
This is could involve executing the script like in method 1 or checking the parameters like in method 2. Pester has a HaveParameter assertion that allows you to check certain things about your function.
HaveParameter documenation, copied from link above:
Get-Command "Invoke-WebRequest" | Should -HaveParameter Uri -Mandatory
function f ([String] $Value = 8) { }
Get-Command f | Should -HaveParameter Value -Type String
Get-Command f | Should -Not -HaveParameter Name
Get-Command f | Should -HaveParameter Value -DefaultValue 8
Get-Command f | Should -HaveParameter Value -Not -Mandatory

Ouput redirection/capturing issue with Powershell and Try-Catch and external EXE

First off, either A) I'm not investigating into this hard enough or B) I've found a problem that requires some funky hack. By the way this is posh v1.0.
Here it goes:
A week or so ago I asked a question about redirecting the output from the exection of an EXE in powershell that was otherwise not being caught. I was swiftly presented with "2>&1" which solved the problem.
Now I've hit another snag and hope to see what some of you stackoverflowers can throw at it.
I'm using try-catch blocks throughout my code as a good programmer should. When I went to place a call to GPG (gnupg.org), passing it a few commands as follows:
try `
{
& $gpgExeLocation --import $keyFileName 2>&1 | out-file "theOutput.txt";
} `
-Catch `
{
write-host "$_";
}
I get a blank text file (theOutput.txt).
But if I do the same call outside of the try-catch block, the text file gets some text written to it as expected.
What I'm wondering is if there is an issue with output redirection to stdout and the way powershell traps exceptions - or if it is my try-catch code to begin with?
here is my try-catch implementation
function global:try
{
param
(
[ScriptBlock]$Command = $(Throw "The parameter -Command is required."),
[ScriptBlock]$Catch = { Throw $_ },
[ScriptBlock]$Finally = {}
)
& {
$local:ErrorActionPreference = "SilentlyContinue"
trap
{
trap
{
& {
trap { Throw $_ }
&$Finally
}
Throw $_
}
$_ | & { &$Catch }
}
&$Command
}
& {
trap { Throw $_ }
&$Finally
}
};
It appears you are using a custom Try function with a -Catch parameter. Mind sharing your implementation to see if that could be causing the problem?
BTW I doubt that your catch statement would ever be invoked unless you are converting the non-terminating error condition of $lastexitode -ne 0 to a terminating error. In this case, you may be better off with a function like this. I use it a lot (it's quite handy):
function Get-CallStack {
trap { continue }
1..100 | foreach {
$var = Get-Variable -scope $_ MyInvocation
$var.Value.PositionMessage -replace "`n"
}
}
#--------------------------------------------------------------------
# Helper function to deal with legacy exe exit codes
#--------------------------------------------------------------------
function CheckLastExitCode {
param ([int[]]$SuccessCodes = #(0), [scriptblock]$CleanupScript=$null)
if ($SuccessCodes -notcontains $LastExitCode) {
if ($CleanupScript) {
"Executing cleanup script: $CleanupScript"
&$CleanupScript
}
$OFS = $NL = [System.Environment]::NewLine
throw "EXE RETURNED EXIT CODE ${LastExitCode}${NL}$(Get-CallStack)"
}
}
Use it like so:
& $gpgExeLocation --import $keyFileName 2>&1 | out-file "theOutput.txt"
CheckLastExitCode