Dealing with Arrays in Powershell (testing with Pester) - powershell

I have a little trouble understanding the way to implement this process. I want to achieve a total count in the score so that if a test successfully passes or fails it can be added into an array. That array will be counted in the length.
This is my code as an example:
#This stores the array of the number of passed and failed test
$passed = #()
$failed = #()
Describe "Template Syntax" {
It "Has a JSON template" {
$fileLocation = "$here\azuredeploy.json"
$fileCheck = $fileLocation | Test-Path
if ($fileCheck -eq $true) { $passed = $passed + 1
Write-Host "1st file exist " }
if ($fileCheck -eq $false) { $failed = $failed + 1
Write-Host "1st file does exist" }
}
It "Has a parameters file" {
$fileLocation ="$here\azuredeploy.parameters*.json"
$fileCheck = $fileLocation | Test-Path
if ($fileCheck -eq $true) { $passed = $passed + 1;
Write-Host "2nd file exist "}
if ($fileCheck -eq $false) { $failed = $failed + 1
Write-Host "2nd file does exist" }
}
PrintArgs
}
function PrintArgs(){
Write-Host -ForegroundColor yellow "Passed: $($passed.Length) Failed: $($failed.Length)"
}
Is there a different way or another approach that I can do to achieve this? I know that pester does it automatically, however, I want to use a Powershell script to test.

Your Pester tests aren't really Pester tests unless you include a Should assertion. I think you should rewrite your tests as follows:
Describe "Template Syntax" {
$fileLocation = "$here\azuredeploy.json"
$fileCheck = $fileLocation | Test-Path
It "Has a JSON template" {
$fileCheck | Should -Be $true
}
$fileLocation ="$here\azuredeploy.parameters*.json"
$fileCheck = $fileLocation | Test-Path
It "Has a parameters file" {
$fileCheck | Should -Be $true
}
}
If you then run this with Invoke-Pester you get a summary of passed and failed test counts at the end automatically. If you need to access these values, you can use -PassThru to return them to a variable. For example:
$Results = Invoke-Pester .\your.tests.ps1 -PassThru
Then you can get the number of passed and failed tests as follows:
$Results.PassedCount
$Results.FailedCount
If you genuinely want to use your own counters (which would mean maintaining lots of unnecessary logic) you could do as follows:
$Passed = 0
$Failed = 0
Describe "Template Syntax" {
$fileLocation = "$here\azuredeploy.json"
$fileCheck = $fileLocation | Test-Path
It "Has a JSON template" {
$fileCheck | Should -Be $true
}
if ($fileCheck -eq $true) { $Passed++ } else { $Failed++ }
$fileLocation ="$here\azuredeploy.parameters*.json"
$fileCheck = $fileLocation | Test-Path
It "Has a parameters file" {
$fileCheck | Should -Be $true
}
if ($fileCheck -eq $true) { $Passed++ } else { $Failed++ }
Write-Host -ForegroundColor yellow "Passed: $Passed Failed: $Failed"
}
Note that the Describe block acts as a kind of script scope, so you have to print the values of $Passed and $Failed within the Describe to get at the values.

Looking at your code, you do not need arrays to count the scores.
Instead of defining $passed and $failed as arrays, just set them up as integer counters with a starting value of 0
$passed = $failed = 0
Then instead of calling function PrintArgs() you simply do
Write-Host -ForegroundColor yellow "Passed: $passed Failed: $failed"
By the way, to increment a counter you can simply do $passed++ instead of $passed = $passed + 1
If you DO insist on using arrays you can change the $passed = $passed + 1 to something like $passed += $true. By doing that you add a new element to the array with a value of $true (or whatever you feel is more appropriate.

Related

List SPN's Script - Write results to file issue

Good morning everyone.
I found this script on the InterWeb's which works phenominal. HOWEVER... No matter what I try, and where I put it, I can't seem to get it to do the results to an out-file.
What the hell am I doing wrong, and where does the variable need to go?
# Source / credit:
# https://social.technet.microsoft.com/wiki/contents/articles/18996.active-directory-powershell-script-to-list-all-spns-used.aspx
cls
$search = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$search.filter = "(servicePrincipalName=*)"
## You can use this to filter for OU's:
## $results = $search.Findall() | ?{ $_.path -like '*OU=whatever,DC=whatever,DC=whatever*' }
$results = $search.Findall()
foreach( $result in $results ) {
$userEntry = $result.GetDirectoryEntry()
Write-host "Object Name = " $userEntry.name -backgroundcolor "yellow" -foregroundcolor "black"
Write-host "DN = " $userEntry.distinguishedName
Write-host "Object Cat. = " $userEntry.objectCategory
Write-host "servicePrincipalNames"
$i=1
foreach( $SPN in $userEntry.servicePrincipalName ) {
Write-host "SPN ${i} =$SPN"
$i+=1
}
Write-host ""
}

Enviroment Paths without overwriting String

I would like to ask question about how I should proceed or how I should fix the code.
My problem is that I need my code to write into the Path three different paths for Logstash, Kibana and ElasticSearch, but I have no idea how to do it. It returns always the same error about missing ")" error
Here's the whole code ¨
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[string]$NewLocation.GetType($ElasticSearch)
[string]$ElasticSearch = "C:\Elastic_Test_Server\elasticsearch\bin"
[string]$Kibana = "C:\Elastic_Test_Server\kibana\bin"
[string]$Logstash = "C:\Elastic_Test_Server\logstash\bin"
)
Begin
{
#Je potřeba spustit jako Administrátor
$regPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
Function GetOldPath()
{
$regKey = $hklm.OpenSubKey($regPath, $FALSE)
$envpath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
return $envPath
}
}
Process
{
# Win32API errory
$ERROR_SUCCESS = 0
$ERROR_DUP_NAME = 34
$ERROR_INVALID_DATA = 13
$NewLocation = $NewLocation.Trim();
If ($NewLocation -eq "" -or $NewLocation -eq $null)
{
Exit $ERROR_INVALID_DATA
}
[string]$oldPath = GetOldPath
Write-Verbose "Old Path: $oldPath"
# Zkontroluje zda cesta již existuje
$parts = $oldPath.split(";")
If ($parts -contains $NewLocation)
{
Write-Warning "The new location is already in the path"
Exit $ERROR_DUP_NAME
}
# Nová cesta
$newPath = $oldPath + ";" + $NewLocation
$newPath = $newPath -replace ";;",""
if ($pscmdlet.ShouldProcess("%Path%", "Add $NewLocation")){
# Přidá to přítomné session
$env:path += ";$NewLocation"
# Uloží do registru
$regKey = $hklm.OpenSubKey($regPath, $True)
$regKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
Write-Output "The operation completed successfully."
}
Exit $ERROR_SUCCESS
}
Thank you for your help.
I really think you could simplify this a lot, unless I have misunderstood. Apologies, I am not currently on a Windows machine so can't test this.
function Add-ESPath {
# Create an array of the paths we wish to add.
$ElasticSearch = #(
"C:\Elastic_Test_Server\elasticsearch\bin",
"C:\Elastic_Test_Server\kibana\bin",
"C:\Elastic_Test_Server\logstash\bin"
)
# Collect the current PATH string and split it out in to an array
$CurrentPath = [System.Environment]::GetEnvironmentVariable("PATH")
$PathArray = $CurrentPath -split ";"
# Loop though the paths we wish to add.
foreach ($Item in $ElasticSearch) {
if ($PathArray -notcontains $Item) {
$PathArray += $Item
}
else {
Write-Output -Message "$Item is already a member of the path." # Use Write-Warning if you wish. I see it more as a notification here.
}
}
# Set the path.
$PathString = $PathArray -join ";"
Try {
[System.Environment]::SetEnvironmentVariable("PATH", $PathString)
exit 0
}
Catch {
Write-Warning -Message "There was an issue setting PATH on this machine. The path was:" # Use $env:COMPUTERNAME here perhaps instead of 'this machine'.
Write-Warning -Message $PathString
Write-Warning -Message $_.Exception.Message
exit 1
}
}
Add-ESPath
Perhaps you want to add some kind of log file rather than writing messages/warnings to the console. You can use Add-Content for this.
I long time ago i wrote some functions to add a path to system path + their is an check if the path is already inside the system path. And i also did an elevation check so when i use this function and i forgot to elevate my powershell that i get a warning. Its a different approach, I hope it will help you.
I only use the begin {} proccess{} statements for when i want to write a function that excepts pipeline inputs. So its if you want to write a function that will work as the following:
$paths = #("C:\Elastic_Test_Server\elasticsearch\bin", "C:\Elastic_Test_Server\kibana\bin")
$paths | my-append-these-to-system-path-function
Elevation check:
function G-AmIelevated($warningMessage){
if([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")){
return $true
}else{
write-host "not elevated $warningMessage" -ForegroundColor Red
return $false
}
}
append something to system path with check if its already inside system path:
function G-appendSystemEnvironmentPath($str){
if(test-path $str){
if(!((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path) -like "*$str*")){
write-host "`t $str exists...`n adding $str to environmentPath" -ForegroundColor Yellow
if(G-AmIelevated){
write-host `t old: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path
Set-ItemProperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' `
-Name Path `
-Value "$((Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path);$str"
write-host `t new: (Get-Itemproperty -path 'hklm:\system\currentcontrolset\control\session manager\environment' -Name Path).Path
write-host `t restart the computer for the changes to take effect -ForegroundColor Red
write-host `t `$Env:Path is the merge of System Path and User Path This function set the system path
write-host `t $str appended to environmet variables. -ForegroundColor Green
}else{
write-host `t rerun ise in elevated mode -ForegroundColor Red
}
}else{
write-host "`t $str is in system environmenth path"
}
}else{
write-host `t $str does not exist
}
}
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\elasticsearch\bin"
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\kibana\bin"
G-appendSystemEnvironmentPath -str "C:\Elastic_Test_Server\logstash\bin"

Powershell script for checking AD replication

I adapted an AD replication powershell script I found online to include the code below:
function ExitWithCode {
param
(
$exitcode
)
$host.SetShouldExit($exitcode)
exit
}
function Write-Log {
<#
.SYNOPSIS
Write-Log writes a message to a logfile
.DESCRIPTION
The Write-Log function is designed to add logging capability to other scripts.
In addition to writing output and/or verbose you can write to a log file for
later debugging.
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[Alias('LogContent')]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet("Error", "Info", "Status")]
[string]$Level = "Info",
[Parameter(Mandatory = $false)]
[Alias('LogPath')]
[string]$Path = 'C:\dataloop\ADHealthCheck.log'
)
BEGIN {
[string]$FormattedDate = Get-Date -Format "dd-MM-yyyy HH:mm"
If (-NOT (Test-Path $path)) {
Write-Verbose "Creating $Path"
[System.IO.FileInfo]$LogFile = New-Item $Path -Force -ItemType file
}
}
PROCESS {
[string]$LogLine = "$FormattedDate - $Level - $message"
$LogLine | Out-File -FilePath $Path -Append
Switch ($Level) {
"Info" {Write-Verbose $LogLine}
"Status" {Write-Output $LogLine}
"Error" {Write-Error $LogLine}
}
}
END {}
}
function Get-ADHealthCheck {
[CmdletBinding()]
param()
BEGIN {
Write-Log "Beginning the AD Health Check..."
}
PROCESS {
$DCs = Get-ADDomainController -Filter * |sort name
Write-Log "$($DCs.Count) Domain Controllers found" -level Info
$results = #()
ForEach ($DC in $DCs) {
Write-Log "Getting replication metadata for $($DC.HostName)" -level Status
$ReplStatuses = Get-ADReplicationPartnerMetadata -target $DC.HostName -PartnerType Both -ErrorAction SilentlyContinue
If ($ReplStatuses) {
Write-Log "$($ReplStatuses.Count) replication links found for $($DC.HostName)" -level Info
ForEach ($ReplStatus in $ReplStatuses) {
$Partner = $ReplStatus.Partner.Split(",")[1].Replace("CN=","")
$results += [pscustomobject] #{
'Source DC' = $DC.HostName.ToUpper()
'Partner DC' = (Get-ADComputer $Partner).DNSHostName.ToUpper()
'Direction' = $ReplStatus.PartnerType
'Type' = $ReplStatus.IntersiteTransportType
'Last Attempt' = $ReplStatus.LastReplicationAttempt
'Last Success' = $ReplStatus.LastReplicationSuccess
'Last Result' = $ReplStatus.LastReplicationResult
}
}
}
Else {
Write-Log "Unable to get replication status for $($DC.HostName)" -level Error
$results += [pscustomobject] #{
'Source DC' = $DC.HostName.ToUpper()
'Partner DC' = "N/A"
Direction = "N/A"
Type = "N/A"
'Last Attempt' = "N/A"
'Last Success' = "N/A"
'Last Result' = "N/A"
}
}
}
ForEach ($result in $results) {
If ("$($results.'Last Result')" -eq "0") {
Write-Log "There were no replication issues found" -Level Info
ExitWithCode -exitcode 0
}
Else {
Write-Log "These domain controllers have replication errors. Please review them..." -Level Error
$error = $results | where {"$($_.'Last Result')" -ne "0"} | select 'Source DC','Partner DC','Direction' | ft -AutoSize
Write-Log $error -Level Error
ExitWithCode -exitcode 2
}
}
}
}
Get-ADHealthCheck
Basically the only issue I'm having now is the last if/else block. I need it to loop through every entry in the $results hash table and if the "Last Result" key only contains "0", then exit with code 0. If it finds any other values, it should output the source, partner, and direction value(s) fromt he hash table.
Currently, if it encounters an issue, it jumps to the else block, outputs the information requested and then runs the ExitWithCode function which eventually kills the script so anything that comes after the error is not checked.
I've been looking at this too long and have been unsuccessful so I'm throwing it out to there since it may just be something simple I'm missing.
Look at your for loop variables
ForEach ($result in $results) {
For each single $result in the $results. In the following if statement you should be looking at one $result but instead you are doing a comparison against all results. Your subexpression syntax here is also not required.
If ("$($results.'Last Result')" -eq "0")
Note that this is perfectly valid code but it will not get you the results you expect. It will return all 'last result's that are 0. So if even one in the whole collection is 0 the true condition will always fire.
So lets just make some minor changes and use the singular $result
If ($result.'Last Result' -eq "0")
That should get you the result you are looking for. I notice that you are looking for equality with the string 0. That will not be an issue here since the LHS sets the type for the comparison. Have a look at this other post to get a better understanding of what PowerShell does here.
Why is $false -eq "" true?
If your last result was the int 0 this would be true
0 -eq "0"
Your logic appears to be flawed as you have mentioned in comments on how to handle overall success and individual failures. Instead of looping through the results like you were I think we need to check the collection as a whole and loop only if errors are encountered.
# Lets check if any of the results contain failure
$failedChecks = $results | Where-object{$_.'Last Result' -ne 0}
# Evaluate $failedChecks as a boolean. If there are no failed checks Else can be assumed that everything is fine.
if($failedChecks){
Write-Log "These domain controllers have replication errors. Please review them..." -Level Error
$error = $failedChecks | select 'Source DC','Partner DC','Direction' | ft -AutoSize | Out-String
Write-Log $error -Level Error
ExitWithCode -exitcode 2
} else {
Write-Log "There were no replication issues found" -Level Info
ExitWithCode -exitcode 0
}

Foreach loop in a if statement

Wrote a small script to check if certain AD groups exists. For some reason it wont loop through the given array. It only writes the first value of the array to the console. When I put a breakpoint on:
foreach ($item in $SecGroupNames)
I see $secGroupNames being filled with the gives values, can anyone help me out? Can't figure it out for this one.
Code:
Import-Module activedirectory
$SecGroupNames = #(
"DB_DATAREADER",
"DB_DATAWRITER",
"DB_OWNER",
"SQL_Public",
"SQL_SA",
"SQL_SecurityAdmin"
)
foreach ($item in $SecGroupNames)
{
If (Get-ADGroup $item)
{
Write-Host -ForegroundColor Yellow "$item Exists!"
return $true;
}
else
{
Write-Host -ForegroundColor Green "$Item Does not exist, Do something!"
return $false;
}
}
Output:
PS C:\Scripts\CreateOUgroups> C:\Scripts\CreateOUgroups\FunctionCheckSecurityGroup.ps1
DB_DATAREADER Exists!
True
It's because of return statements. It causes the script to return value and end execution in the first loop pass.
If you want to return multiple values from script or function, use Write-Output instead of return.
foreach ($item in $SecGroupNames)
{
if (Get-ADGroup $item)
{
Write-Host -ForegroundColor Yellow "$item Exists!"
Write-Output $true;
}
else
{
Write-Host -ForegroundColor Green "$item Does not exist, Do something!"
Write-Output $false;
}
}

Is the first element of a powershell array always "true"?

arg1I have some code that creates an empty powershell array, then adds items to this array in a do..while loop. For some reason, no matter what I do, the first element in the array is "true". Why is this?
In my code below, you'll notice the loop adds output a line at a time from an external exe. The exe never returns "true", and you can see I'm doing Write-Host for each it that gets added to the array, and there's never a "true" output from this Write-Host call. There is no other code anywhere in my script that adds any elements to the array. This is frustrating because the index of the array is messed up. This essentially makes this array 1-indexed instead of 0-indexed. Does anyone have any ideas about what's happening here?
Also, one more thing to notice, after I initialize the array, i Write-Host the length, and it's 0, then after I'm dont adding all items, the length is what I would expect it to be if "true" were not the first element. So if there's 4 lines returned from the external app, then the second Write-Host $output.Length call will output 4.
Here is my code:
$output = #()
Write-Host "OUTPUT LENGTH START: $($output.Length)" -ForegroundColor Yellow
$continue = $true
do {
$tempoutput = $Process.StandardOutput.ReadLine()
if(($tempoutput -ne $null) -and ([string]::IsNullOrWhiteSpace($tempoutput) -ne $true)) {
Write-Host $tempoutput -ForegroundColor DarkGray
$output += $tempoutput
} else {
$continue = $false
}
}
while($continue -eq $true)
Write-Host "OUTPUT LENGTH END: $($output.Length)" -ForegroundColor Yellow
Also, at the end of my function, i'm returning the output array like this:
$output
And then in my code that calls my function, if I do a foreach over the returned array's elements, "True" will always be the first item in this array.
EDIT TO INCLUDE FULL FUNCTION
Here is my full function with all the Write-Host calls removed:
function Execute-HttpManager($arguments, $filepath){
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
$ProcessInfo.FileName = $filepath
$ProcessInfo.RedirectStandardError = $true
$ProcessInfo.RedirectStandardOutput = $true
$ProcessInfo.RedirectStandardInput = $true
$ProcessInfo.UseShellExecute = $false
$ProcessInfo.CreateNoWindow = $true
$ProcessInfo.Arguments = $arguments
$Process = New-Object System.Diagnostics.Process
$Process.StartInfo = $ProcessInfo
$Process.Start()
$Process.WaitForExit()
$output = #()
$continue = $true
do {
$tempoutput = $Process.StandardOutput.ReadLine()
if(($tempoutput -ne $null) -and ([string]::IsNullOrWhiteSpace($tempoutput) -ne $true)) {
$output += $tempoutput
} else {
$continue = $false
}
}
while($continue -eq $true)
$deployError = $Process.StandardError.ReadToEnd()
$exitcode = $Process.ExitCode
if($exitcode -eq 4) {
$deployagainresponse = Read-Host
if($deployagainresponse -eq 'y') {
Deploy-Database $localapp $localsqlinstance $localdatabasename $localbackup $localsnapshot
} elseif($deployagainresponse -ne 'bypass') {
throw "http interaction failed with error. Check the output."
}
} elseif ($exitcode -ne 0) {
throw "Database deploy failed."
}
return $output
}
And here is the code that calls my function:
$tokenoutput = Execute-HttpManager #("arg1", "arg2", "arg3", "arg4") $pathtoexecutable
It is this $tokenoutput variable that has the "True" added to the beginning of it's array.
Make sure to check for any calls that are returning a value (such as $Process.Start()), and either precede them with [void] or pipe to out-null: $Process.Start() | Out-Null.
Be aware of this for any function! Anything the function outputs is part of the return value.