Method returning string but is assigned as string[] - powershell

I have this code:
[string]$emailBody = getEmailBody $firstName $emailTemplateFileContent
function getEmailBody($firstName, $emailTemplateFileContent)
{
$sb = New-Object Text.StringBuilder
for ($i = 1; $i -lt $emailTemplateFileContent.Length; $i++)
{
$sb.AppendLine($emailTemplateFileContent[$i])
}
$emailTemplateText = $sb.ToString()
$emailTemplateTextCustomised = $emailTemplateText.Replace("#name", $firstName)
return $emailTemplateTextCustomised
}
When I type $emailTemplateTextCustomised.getType() I can see that it is a string.
However when I type $emailBody.getType() I can see that it is an Array.
I can also see that the array has 8 strings, each string containing the output from getEmailBody().
UPDATE:
Powershell seems really buggy, it is no longer a String[] but just a String with 8 repetitions of the output from getEmailBody().
Why is it doing this?
Thanks in advance.

PowerShell isn't buggy, well at least not in this case. :-) You have to understand that in PowerShell the "output" of a function is anything that is not captured to a variable. The line that does the StringBuilder.AppendLine() returns the StringBuilder and that is added to the output of your function. Try this:
function getEmailBody($firstName, $emailTemplateFileContent)
{
$sb = New-Object Text.StringBuilder
for ($i = 1; $i -lt $emailTemplateFileContent.Length; $i++)
{
$sb.AppendLine($emailTemplateFileContent[$i]) > $null
}
$emailTemplateText = $sb.ToString()
$emailTemplateText.Replace("#name", $firstName)
}
If you are on V3 (maybe V2) you can use the -replace operator as well:
function getEmailBody($firstName, $emailTemplateFileContent)
{
$sb = New-Object Text.StringBuilder
for ($i = 1; $i -lt $emailTemplateFileContent.Length; $i++)
{
$sb.AppendLine($emailTemplateFileContent[$i]) > $null
}
$emailTemplateText = $sb.ToString() -replace '#name',$firstName
$emailTemplateText
}

Related

How to use powershell to reorder a string to obfuscate a hidden message?

Just for fun a friend and I are trying to find a creative way to send coded messages to eachother using steganography.I stumbled upon doing something like whats shown below and I have been struggling trying to write a function to automate the process.
this is a secret message
can be turned into:
("{2}{1}{0}{3}"-f'ecret m','is a s','this ','essage')
splitting the string and using reordering seems to be the way to go.
So the string needs to be split in random splits between 5-10 characters
.
The index of the original positions need to be saved
the splits need to be swapped around
and the new indexes sorted as to reorder the message properly
i've just really been struggling
help is appreciated
Just for fun .... 😉🤡
$InputMessage = 'this is a secret message'
$SplittedString = $InputMessage -split '' | Select-Object -Skip 1 | Select-Object -SkipLast 1
[array]::Reverse($SplittedString)
foreach ($Character in $SplittedString) {
if ($Character -notin $CharacterList) {
[array]$CharacterList += $Character
}
}
foreach ($Character in ($InputMessage -split '' | Select-Object -Skip 1 | Select-Object -SkipLast 1)) {
$Index = [array]::indexof($CharacterList, $Character)
$Output += "{$Index}"
}
$Result = "'$Output' -f $(($CharacterList | ForEach-Object {"'$_'"}) -join ',')"
$Result
And the output of this would be:
'{6}{10}{9}{3}{5}{9}{3}{5}{2}{5}{3}{0}{8}{7}{0}{6}{5}{4}{0}{3}{3}{2}{1}{0}' -f 'e','g','a','s','m',' ','t','r','c','i','h'
And the output of this would be:
this is a secret message
And now if you want to go fancy with it you remove the curly braces and the quotes and the commas and the -f and add only the numbers and characters to the data. ;-)
Not exactly what you're looking for but this might give you something to start with:
class Encode {
[string] $EncodedMessage
[int[]] $Map
[int] $EncodingComplexity = 3
Encode ([string] $Value) {
$this.Shuffle($Value)
}
Encode ([string] $Value, [int] $Complexity) {
$this.EncodingComplexity = $Complexity
$this.Shuffle($Value)
}
[void] Shuffle([string] $Value) {
$set = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!##$%^&*()_-+=[{]};:<>|./?'
$ref = [Collections.Generic.HashSet[int]]::new()
$ran = [random]::new()
$enc = [char[]]::new($Value.Length * $this.EncodingComplexity)
for($i = 0; $i -lt $enc.Length; $i++) {
$enc[$i] = $set[$ran.Next($set.Length)]
}
for($i = 0; $i -lt $Value.Length; $i++) {
do {
$x = $ran.Next($enc.Length)
} until($ref.Add($x))
$enc[$x] = $Value[$i]
}
$this.EncodedMessage = [string]::new($enc)
$this.Map = $ref
}
}
class Decode {
static [string] DecodeMessage ([Encode] $Object) {
return [Decode]::DecodeMessage($Object.EncodedMessage, $Object.Map, $Object.EncodingComplexity)
}
static [string] DecodeMessage ([string] $EncodedMessage, [int[]] $Map) {
return [Decode]::DecodeMessage($EncodedMessage, $Map, 3)
}
static [string] DecodeMessage ([string] $EncodedMessage, [int[]] $Map, [int] $Complexity) {
$decoded = [char[]]::new($EncodedMessage.Length / $Complexity)
for($i = 0; $i -lt $decoded.Length; $i++) {
$decoded[$i] = $EncodedMessage[$Map[$i]]
}
return [string]::new($decoded)
}
}
Encoding a message:
PS /> $message = 'this is a secret message'
PS /> $encoded = [Encode] $message
PS /> $encoded
EncodingComplexity EncodedMessage Map
------------------ -------------- ---
3 B$h^elu2w#CeeHH^qa siQJ)t}es:.a3 ema=eN(GiIcsO;tst1 .fsg}eSUk7ms4 N>rfe# {49, 2, 41, 27…}
For decoding the message you can either use the object of the type Encode or you can give your friend the Encoded Message and the Map to decode it ;)
PS /> [Decode]::DecodeMessage($encoded)
this is a secret message
PS /> [Decode]::DecodeMessage('B$h^elu2w#CeeHH^qa siQJ)t}es:.a3 ema=eN(GiIcsO;tst1 .fsg}eSUk7ms4 N>rfe#', $encoded.Map)
this is a secret message

Converting strings to timespans, $PSItem in 'switch'?

I have a bunch of strings, in the form of:
'3m 36s', '24m 38s', '59s'
, to be converted to timespans. My current "solution" is:
'3m 36s', '24m 38s', '59s' |ForEach-Object {
$s = 0
$m = 0
$h = 0
$PSItem.Split(' ') |ForEach-Object {
$item = $PSItem
switch ($PSItem[-1])
{
's'
{
$s = $item.TrimEnd('s')
}
'm'
{
$m = $item.TrimEnd('m')
}
'h'
{
$h = $item.TrimEnd('h')
}
Default
{
Write-Error 'Ooops...' -ErrorAction Stop
}
}
}
$timespan = New-TimeSpan -Hours $h -Minutes $m -Seconds $s
# ToString() is used just to get some easy to read output
$timespan.ToString()
}
While it seems to work for me, I have two issues with the above:
Is the general approach
ForEach -> Split(' ') -> ForEach -> switch
OK-ish? Are there any alternative/better ways of doing the conversion?
I tried using $PSItem in the switch
It seems that the switch construct has it's "own pipeline"
# $item = $PSItem
switch ($PSItem[-1])
{
's'
{
$PSItem
}
}
-- in the above $PSItem evaluates to 's'(, 'm', the value matched). What is actually going on? (internaly?)
I would take one ForEach loop out of things by performing that loop with the Switch command. Here's what I'd end up with:
'3m 36s', '59s', '24m 38s' |%{
$TSParams = #{}
Switch($_.Split()){
{$_[-1] -eq 's'}{$TSParams.Add('Seconds', ([int]$_.trim('s')))}
{$_[-1] -eq 'm'}{$TSParams.Add('Minutes', ([int]$_.trim('m')))}
{$_[-1] -eq 'h'}{$TSParams.Add('Hours', ([int]$_.trim('h')))}
}
New-TimeSpan #TSParams
}
For each string it creates an empty hashtable, then loops through each item of the Split() method, adding the appropriate time to the hashtable. Then it splats that to the New-TimeSpan command, and moves to the next item in the ForEach loop. I tried it locally and had some issues initially when the numbers did not cast as an int, and it tried to convert them to a DateTime, which is why I type cast them in the above code.

PowerShell DataTable removing empty columns logic incorrect

This is the first time that I'm writing a function that can call itself. I'm trying to remove all the columns of a DataTable where the rows are empty.
The code works fine but spits out some errors. For one reason or another it's looping one last time through the for loop with the old $Columns number still in memory. Although I'm filling it again each time the function is called... I don't understand why..
The code:
Function Remove-EmptyColumns {
$Columns = $DataTable.Columns.Count
$Rows = $DataTable.Rows.Count
for ($c = 0; $c -lt $Columns; $c++) {
$Empty = 0
for ($r = 0; $r -lt $Rows; $r++) {
if ($DataTable.Rows[$r].Item($c).ToString() -eq '') {
$Empty++
}
}
if ($Empty -eq $Rows) {
$DataTable.Columns.Remove($DataTable.Columns[$c])
Remove-EmptyColumns
}
}
}
Thank you for your help.
Why is it a recursive method? Try removing the Remove-EmptyColumns and it should work, no?
EDIT: Try
Function Remove-EmptyColumns {
$Columns = $DataTable.Columns.Count
$Rows = $DataTable.Rows.Count
$columnsToRemove = #()
for ($c = 0; $c -lt $Columns; $c++) {
$Empty = 0
for ($r = 0; $r -lt $Rows; $r++) {
if ($DataTable.Rows[$r].Item($c).ToString() -eq '') {
$Empty++
}
}
if ($Empty -eq $Rows) {
$columnsToRemove.Add($DataTable.Columns[$c])
}
}
$columnsToRemove | ForEach-Object {
$DataTable.Columns.Remove($_) }
}
Just a thought, but I like to use the String static method of IsNullOrEmpty.
if ([string]::IsNullOrEmpty($DataTable.Rows[$r].Item($c).ToString()))
{ ... }
This removes issues where the value may not equal '' but be $Null instead and it does it in a single line.

Is it possible to include functions only without executing the script?

Say I have MyScript.ps1:
[cmdletbinding()]
param (
[Parameter(Mandatory=$true)]
[string] $MyInput
)
function Show-Input {
param ([string] $Incoming)
Write-Output $Incoming
}
function Save-TheWorld {
#ToDo
}
Write-Host (Show-Input $MyInput)
Is it possible to dot source the functions only somehow? The problem is that if the script above is dot sourced, it executes the whole thing...
Is my best option to use Get-Content and parse out the functions and use Invoke-Expression...? Or is there a way to access PowerShell's parser programmatically? I see this might be possible with PSv3 using [System.Management.Automation.Language.Parser]::ParseInput but this isn't an option because it has to work on PSv2.
The reason why I'm asking is that i'm trying out the Pester PowerShell unit testing framework and the way it runs tests on functions is by dot sourcing the file with the functions in the test fixture. The test fixture looks like this:
MyScript.Tests.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"
Describe "Show-Input" {
It "Verifies input 'Hello' is equal to output 'Hello'" {
$output = Show-Input "Hello"
$output.should.be("Hello")
}
}
Using Doug's Get-Function function you could include the functions this way:
$script = get-item .\myscript.ps1
foreach ($function in (get-function $script))
{
$startline = $function.line - 1
$endline = $startline
$successful = $false
while (! $successful)
{
try {
$partialfunction = ((get-content $script)[$startline..$endline]) -join [environment]::newline
invoke-expression $partialfunction
$successful = $true
}
catch [Exception] { $endline++ }
}
}
Edit: [System.Management.Automation.IncompleteParseException] can be used instead of [Exception] in Powershell V2.
Note -- if you find this answer helpful please upvote jonZ's answer as I wouldn't of been able to come up with this if it weren't for his helpful answer.
I created this function extractor function based on the script #jonZ linked to. This uses [System.Management.Automation.PsParser]::Tokenize to traverse all tokens in the input script and parses out functions into function info objects and returns all function info objects as an array. Each object looks like this:
Start : 99
Stop : 182
StartLine : 7
Name : Show-Input
StopLine : 10
StartColumn : 5
StopColumn : 1
Text : {function Show-Input {, param ([string] $Incoming), Write-Output $Incoming, }}
The text property is a string array and can be written to temporary file and dot sourced in or combined into a string using a newline and imported using Invoke-Expression.
Only the function text is extracted so if a line has multiple statements such as: Get-Process ; function foo () { only the part relevant to the function will be extracted.
function Get-Functions {
param (
[Parameter(Mandatory=$true)]
[System.IO.FileInfo] $File
)
try {
$content = Get-Content $File
$PSTokens = [System.Management.Automation.PsParser]::Tokenize($content, [ref] $null)
$functions = #()
#Traverse tokens.
for ($i = 0; $i -lt $PSTokens.Count; $i++) {
if($PSTokens[$i].Type -eq 'Keyword' -and $PSTokens[$i].Content -eq 'Function' ) {
$fxStart = $PSTokens[$i].Start
$fxStartLine = $PSTokens[$i].StartLine
$fxStartCol = $PSTokens[$i].StartColumn
#Skip to the function name.
while (-not ($PSTokens[$i].Type -eq 'CommandArgument')) {$i++}
$functionName = $PSTokens[$i].Content
#Skip to the start of the function body.
while (-not ($PSTokens[$i].Type -eq 'GroupStart') -and -not ($PSTokens[$i].Content -eq '{')) {$i++ }
#Skip to the closing brace.
$startCount = 1
while ($startCount -gt 0) { $i++
if ($PSTokens[$i].Type -eq 'GroupStart' -and $PSTokens[$i].Content -eq '{') {$startCount++}
if ($PSTokens[$i].Type -eq 'GroupEnd' -and $PSTokens[$i].Content -eq '}') {$startCount--}
}
$fxStop = $PSTokens[$i].Start
$fxStopLine = $PSTokens[$i].StartLine
$fxStopCol = $PSTokens[$i].StartColumn
#Extract function text. Handle 1 line functions.
$fxText = $content[($fxStartLine -1)..($fxStopLine -1)]
$origLine = $fxText[0]
$fxText[0] = $fxText[0].Substring(($fxStartCol -1), $fxText[0].Length - ($fxStartCol -1))
if ($fxText[0] -eq $fxText[-1]) {
$fxText[-1] = $fxText[-1].Substring(0, ($fxStopCol - ($origLine.Length - $fxText[0].Length)))
} else {
$fxText[-1] = $fxText[-1].Substring(0, ($fxStopCol))
}
$fxInfo = New-Object -TypeName PsObject -Property #{
Name = $functionName
Start = $fxStart
StartLine = $fxStartLine
StartColumn = $fxStartCol
Stop = $fxStop
StopLine = $fxStopLine
StopColumn = $fxStopCol
Text = $fxText
}
$functions += $fxInfo
}
}
return $functions
} catch {
throw "Failed in parse file '{0}'. The error was '{1}'." -f $File, $_
}
}
# Dumping to file and dot sourcing:
Get-Functions -File C:\MyScript.ps1 | Select -ExpandProperty Text | Out-File C:\fxs.ps1
. C:\fxs.ps1
Show-Input "hi"
#Or import without dumping to file:
Get-Functions -File C:\MyScript.ps1 | % {
$_.Text -join [Environment]::NewLine | Invoke-Expression
}
Show-Input "hi"

Powershell - concatenating an array reference with another variable

I'm trying to concatenate a variable with a value from an array, and having problems.
The output I get is the variable value, plus the ENTIRE array, not just the desired value (index of $i).
$Database = "Checklist.dbo."
$ExtractTables = #("Page"
, "HotelOwner"
, "Hotel"
)
for ($i=0; $i -le $ExtractTables.Length – 1; $i++) {
write-host $Database$ExtractTables[$i] # <<<<<<< takes ENTIRE array
}
My ultimate goal is to call an executable (bcp) something like this:
# & bcp $Database$ExtractTables[$i] out $OutputDirectory$ExtractTables[$i].txt -c -T -SCHELDEV02
Any pointers for a newbie?
Thanks!
$Database = "Checklist.dbo."
$ExtractTables = #("Page"
, "HotelOwner"
, "Hotel"
)
for ($i=0; $i -le $ExtractTables.Length – 1; $i++) {
write-host "$Database$($ExtractTables[$i])"
}
To evaluate a sub-expression before the rest of the expression put it inside $()
$Database = "Checklist.dbo."
$ExtractTables = #("Page"
, "HotelOwner"
, "Hotel"
)
$ExtractTables | ForEach {
write-host "$Database$_"
}