PowerShell 5.0 Class Method Returns "Not all code path returns value within method" - powershell

As an experiment with PowerShell 5.0 classes I tried to translate JavaScript code for the Stable Marriage problem at Rosetta Code. It seemed very straight forward, but The second method (Rank) returns the error: Not all code path returns value within method.
class Person
{
# --------------------------------------------------------------- Properties
hidden [int]$CandidateIndex = 0
[string]$Name
[person]$Fiance = $null
[person[]]$Candidates = #()
# ------------------------------------------------------------- Constructors
Person ([string]$Name)
{
$this.Name = $Name
}
# ------------------------------------------------------------------ Methods
static [void] AddCandidates ([person[]]$Candidates)
{
[Person]::Candidates = $Candidates
}
[int] Rank ([person]$Person)
{
for ($i = 0; $i -lt $this.Candidates.Count; $i++)
{
if ($this.Candidates[$i] -eq $Person)
{
return $i
}
return $this.Candidates.Count + 1
}
}
[bool] Prefers ([person]$Person)
{
return $this.Rank($Person) -lt $this.Rank($this.Fiance)
}
[person] NextCandidate ()
{
if ($this.CandidateIndex -ge $this.Candidates.Count)
{
return $null
}
return $this.Candidates[$this.CandidateIndex++]
}
[int] EngageTo ([person]$Person)
{
if ($Person.Fiance)
{
$Person.Fiance.Fiance = $null
}
return $this.Fiance = $Person
}
[void] SwapWith ([person]$Person)
{
Write-Host ("{0} and {1} swap partners" -f $this.Name, $Person.Name)
$thisFiance = $this.Fiance
$personFiance = $Person.Fiance
$this.EngageTo($personFiance)
$Person.EngageTo($thisFiance)
}
}

The error is because if $this.Candidates.Count is 0, no return will execute.
Should the second return be outside of your for loop?
The current way, if it does not match the first candidate, it will return $this.Candidates.Count + 1.
[int] Rank ([person]$Person)
{
for ($i = 0; $i -lt $this.Candidates.Count; $i++)
{
if ($this.Candidates[$i] -eq $Person)
{
return $i
}
}
return $this.Candidates.Count + 1
}

Related

speed up inserting data from Data Set to SQL powershell

When I run this script to insert data into table in SQL from DataSet, the process takes a long time to insert it (row by row) .the inserting time depend on the row counts , more rows more time .
is there any way to speed up the process or split the rows to multiple records and insert it parallel on same time .
for ( $i = 0; $i -lt $DataSet.Tables[0].Rows.Count; $i++) {
try {
$valuestr = New-Object -TypeName System.Text.StringBuilder
for ( $x = 0; $x -lt 11; $x++) {
if ($x -lt 10) {
[void]$valuestr.Append("'" + $DataSet.Tables[0].Rows[$i][$x].ToString().Trim().Replace("'", "/") + "',")
}
else {
[void]$valuestr.Append("'" + $DataSet.Tables[0].Rows[$i][$x].ToString().Trim().Replace("'", "/") + "'")
}
}
[string]$inputstr = $valuestr.ToString()
[char[]]$values = $inputstr.ToCharArray()
[string]$output = ''
foreach ($letter in $values) {
[int]$value = [Convert]::ToInt32($letter)
[string]$hexOutput = [String]::Format("{0:X}", $value)
switch ($hexOutput) {
"627" { $output += "ا"; Break }
default { $output += [Convert]::Tostring($letter); break }
}
}
$sqlCmdd.CommandText = "INSERT INTO $sqlTable ([ICN],[ICN_NODE],[PARTITION_ID],[ICN_CREATE_DT] ,[ICN_SEQ_NUM],[ICN_SUBNUM],[ICN_TAG_ID],[ICN_TAG_VER],[BLK_NUM],[BLK_LEN] ,[FREE_FORM_TXT]) Values (" + $output.ToString() + ")"
$sqlCmdd.ExecuteNonQuery()
}
catch { $_.Exception.Message | Out-File C:\log\log.txt -Append }
}

Show content of hashtable when Pester test case fails

Problem
When a Hashtable is used as input for Should, Pester outputs only the typename instead of the content:
Describe 'test' {
It 'test case' {
$ht = #{ foo = 21; bar = 42 }
$ht | Should -BeNullOrEmpty
}
}
Output:
Expected $null or empty, but got #(System.Collections.Hashtable).
Expected output like:
Expected $null or empty, but got #{ foo = 21; bar = 42 }.
Cause
Looking at Pester source, the test input is formatted by private function Format-Nicely, which just casts to String if the value type is Hashtable. This boils down to calling Hashtable::ToString(), which just outputs the typename.
Workaround
As a workaround I'm currently deriving a class from Hashtable that overrides the ToString method. Before passing the input to Should, I cast it to this custom class. This makes Pester call my overridden ToString method when formatting the test result.
BeforeAll {
class MyHashTable : Hashtable {
MyHashTable( $obj ) : base( $obj ) {}
[string] ToString() { return $this | ConvertTo-Json }
}
}
Describe 'test' {
It 'test case' {
$ht = #{ foo = 21; bar = 42 }
[MyHashTable] $ht | Should -BeNullOrEmpty
}
}
Now Pester outputs the Hashtable content in JSON format, which is good enough for me.
Question
Is there a more elegant way to customize Pester output of Hashtable, which doesn't require me to change the code of each test case?
Somewhat of a hack, override Pester's private Format-Nicely cmdlet by defining a global alias of the same name.
BeforeAll {
InModuleScope Pester {
# HACK: make private Pester cmdlet available for our custom override
Export-ModuleMember Format-Nicely
}
function global:Format-NicelyCustom( $Value, [switch]$Pretty ) {
if( $Value -is [Hashtable] ) {
return $Value | ConvertTo-Json
}
# Call original cmdlet of Pester
Pester\Format-Nicely $Value -Pretty:$Pretty
}
# Overrides Pesters Format-Nicely as global aliases have precedence over functions
New-Alias -Name 'Format-Nicely' -Value 'Format-NicelyCustom' -Scope Global
}
This enables us to write test cases as usual:
Describe 'test' {
It 'logs hashtable content' {
$ht = #{ foo = 21; bar = 42 }
$ht | Should -BeNullOrEmpty
}
It 'logs other types regularly' {
$true | Should -Be $false
}
}
Log of 1st test case:
Expected $null or empty, but got #({
"foo": 21,
"bar": 42
}).
Log of 2nd test case:
Expected $false, but got $true.
A cleaner (albeit more lengthy) way than my previous answer is to write a wrapper function for Should.
Such a wrapper can be generated using System.Management.Automation.ProxyCommand, but it requires a little bit of stitchwork to generate it in a way that it works with the dynamicparam block of Should. For details see this answer.
The wrappers process block is modified to cast the current pipeline object to a custom Hashtable-derived class that overrides the .ToString() method, before passing it to the process block of the original Should cmdlet.
class MyJsonHashTable : Hashtable {
MyJsonHashTable ( $obj ) : base( $obj ) {}
[string] ToString() { return $this | ConvertTo-Json }
}
Function MyShould {
[CmdletBinding()]
param(
[Parameter(Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)]
[System.Object]
${ActualValue}
)
dynamicparam {
try {
$targetCmd = $ExecutionContext.InvokeCommand.GetCommand('Pester\Should', [System.Management.Automation.CommandTypes]::Function, $PSBoundParameters)
$dynamicParams = #($targetCmd.Parameters.GetEnumerator() | Microsoft.PowerShell.Core\Where-Object { $_.Value.IsDynamic })
if ($dynamicParams.Length -gt 0)
{
$paramDictionary = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in $dynamicParams)
{
$param = $param.Value
if(-not $MyInvocation.MyCommand.Parameters.ContainsKey($param.Name))
{
$dynParam = [Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
$paramDictionary.Add($param.Name, $dynParam)
}
}
return $paramDictionary
}
} catch {
throw
}
}
begin {
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Pester\Should', [System.Management.Automation.CommandTypes]::Function)
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline()
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process {
try {
# In case input object is a Hashtable, cast it to our derived class to customize Pester output.
$item = switch( $_ ) {
{ $_ -is [Hashtable] } { [MyJsonHashTable] $_ }
default { $_ }
}
$steppablePipeline.Process( $item )
} catch {
throw
}
}
end {
try {
$steppablePipeline.End()
} catch {
throw
}
}
}
To override Pesters Should by the wrapper, define a global alias like this:
Set-Alias Should MyShould -Force -Scope Global
And to restore the original Should:
Remove-Alias MyShould -Scope Global
Notes:
I have also changed the argument of GetCommand() from Should to Pester\Should to avoid recursion due to the alias. Not sure if this is actually necessary though.
A recent version of Pester is required. Failed with Pester 5.0.4 but tested successfully with Pester 5.1.1.

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.

Method returning string but is assigned as string[]

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
}

Combination without Repetition using powershell?

I am new in powershell but was wondering how do you get combination of given numbers without repetition ?
for e.g
$a = (1,2,3,4)
$n = 4
$k = 2
output :-
12
13
14
23
24
34
if K = 3 then
123
124
134
234
Just a quick solution for that particular case, I don't see much value in it because I feel there are tools designed for such exercises... ;)
$a = (1,2,3,4)
$rest = New-Object Collections.ArrayList
$a[1..($a.Length - 1)] | foreach { $rest.Add($_) | Out-Null }
$prefix = $a[0]
$(while ($rest) {
foreach ($suffix in $rest) {
-join ($prefix, $suffix)
}
$prefix = $rest[0]
$rest.RemoveAt(0)
}) -join ', '
You can try this :
$List = "A","B","C","D","E","F","G","H"
$k = 3
Add-Type #"
public class Shift {
public static int Right(int x, int count) { return x >> count; }
public static uint Right(uint x, int count) { return x >> count; }
public static long Right(long x, int count) { return x >> count; }
public static ulong Right(ulong x, int count) { return x >> count; }
public static int Left(int x, int count) { return x << count; }
public static uint Left(uint x, int count) { return x << count; }
public static long Left(long x, int count) { return x << count; }
public static ulong Left(ulong x, int count) { return x << count; }
}
"#
function CombinationWithoutRepetition ([int]$k, $List)
{
Function IsNBits ([long]$value, $k, $length)
{
$count = 0
for ($i = 0 ; $i -le $length ; $i++)
{
if ($value -band 1)
{
$count++
}
$value = [shift]::Right($value,1)
}
if ($count -eq $k)
{
return $true
}
else
{
return $false
}
}
Function BitsToArray ([long]$value, $List)
{
$res = #()
for ($i = 0 ; $i -le $List.length ; $i++)
{
if ($value -band 1)
{
$res += $List[$i]
}
$value = [shift]::Right($value,1)
}
return ,$res
}
[long]$i = [Math]::Pow(2, $List.Length)
$res = #()
for ([long]$value=0 ; $value -le $i ; $value++)
{
if ((IsNBits $value $k $List.Length) -eq $true)
{
#write-host $value
$res += ,(BitsToArray $value $List)
}
}
return ,$res
}
Clear-Host
$res = CombinationWithoutRepetition $k $List
$res.count
$res | %{$_ |% {}{Write-Host -NoNewline $_}{Write-Host ""}}
I thought of this as binary strings with 1's and 0's, generated each binary string from 1 to Length of the array, and then use the value at the same index if there is a 1.
$items = 1,2,3,4;
$k = 3;
$output = #();
for ($i = 1; $i -lt [Math]::Pow(2, $items.Count); $i++) {
$binary = [Convert]::ToString($i, 2).PadLeft($items.Count, "0");
# if number of 1's in the entire string is not equal to "1" * $k, then skip
if (($binary -replace "0", "") -ne ("1" * $k)) {
continue;
}
#foreach char in binary
$outputSet = #()
$outputSet += $items | ForEach-Object {
if ($binary[$items.IndexOf($_)] -eq "1") {
$_
}
}
$output += [PSCustomObject]#{
Set = $outputSet;
Binary = $binary;
Count = $outputSet.Count;
Concatenated = $outputSet -join "";
}
}
$output | Sort-Object -Property Count, Set
#SAMPLE OUTPUT
Set Binary Count Concatenated
--- ------ ----- ------------
{1, 2, 3} 1110 3 123
{1, 2, 4} 1101 3 124
{1, 3, 4} 1011 3 134
{2, 3, 4} 0111 3 234