Please pardon me if I have misstated the title, but here is what I am looking for:
Let's say I have this script:
$secureCiphers = #(
'AES 128/128',
'AES 256/256'
)
foreach ($secureCipher in $secureCiphers) {
$key = (Get-Item HKLM:\).OpenSubKey('SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers', $true).CreateSubKey($secureCipher)
New-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\$secureCipher" -name 'Enabled' -value '0xffffffff' -PropertyType 'DWord' -Force | Out-Null
$key.close()
Write-Host "Strong cipher $secureCipher has been enabled."
}
Is there something out there will unroll the loop, make assignments for each iteration and say the above code is equivalent to:
$key = (Get-Item HKLM:\).OpenSubKey('SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers', $true).CreateSubKey('AES 128/128')
New-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\AES 128/128" -name 'Enabled' -value '0xffffffff' -PropertyType 'DWord' -Force | Out-Null
$key.close()
Write-Host "Strong cipher AES 128/128 has been enabled."
$key = (Get-Item HKLM:\).OpenSubKey('SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers', $true).CreateSubKey('AES 256/256')
New-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\AES 256/256" -name 'Enabled' -value '0xffffffff' -PropertyType 'DWord' -Force | Out-Null
$key.close()
Write-Host "Strong cipher AES 256/256 has been enabled."
I can then write an automated test to say the script was supposed to set these registry keys and the system has the expected values for those registry keys. Does this request make sense? The scripts are more complicated and have functions and a lot of if-else statements.
I think instead what might serve you better is to abstract the logic of what you're trying to do into functions (preferably in a module), which take parameters for the things that change. So in this case, you'd have a function that takes a particular "secure cipher" and does what it needs to do.
Once you've got the logic in a function, you can write tests around the function, that ensure it works as expected, that it fails as expected when passed invalid entries, etc.
PowerShell has a testing framework called Pester that is helpful for writing tests.
Now you've got re-usable function, with tests to prove it works.
Your script should then become:
$secureCiphers = #(
'AES 128/128',
'AES 256/256'
)
foreach ($secureCipher in $secureCiphers) {
Set-MySecureCipher -Cipher $secureCipher
}
Or, if you wrote your function to be able to accept pipeline input, it may be:
$secureCiphers | Set-MySecureCipher
I've left out the meaty details of a lot of this in favor of explaining at a high-level, but you can continue to research each of those areas:
Writing functions
Writing PowerShell modules
Writing Pester tests
Writing advanced functions (accepting pipeline input, supporting ShouldProcess for -WhatIf support)
Related
I'm new to PowerShell and am trying to create a script that goes through a csv file (simple name,value csv) and loads each new line in it as a variable and then runs a function against that set of variables.
I've had success at getting it to work for 1 variable by using the following code snippet:
Import-Csv -Path C:\something\mylist.csv | ForEach-Object {
New-Variable -Name $_.Name -Value $_.Value -Force
}
My csv looks like this:
name,value
RegKey1,"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\LanmanWorkstation"
Basically it's a list of registry keys each named as RegKey# and then the path of that reg key is the intended value of the variable.
I'm currently playing around with the "Test-Path" cmdlet that just prints out true/false if the passed reg-key exists and then just prints out some text based on if it found the reg key or not.
That snippet looks like so:
Test-Path $RegKey1
IF ($LASTEXITCODE=0) {
Write-Output "It worked"
}
else {
Write-Output "It didn't work"
}
This works fine however what I'm trying to achieve is for powershell to run this cmdlet against each of the lines in the csv file - basically checking each reg key in it and then doing whatever specified to it.
What I'm trying to avoid is declaring hundreds of variables for every regkey I plan on using but instead have this one function that just runs through the csv and every time it runs, it increments the number next to the variable's name - RegKey1,RegKey2,RegKey3 etc.
Let me know if there's a way to do this in powershell or a better way of approaching this altogether. I also apologize in advance if I've not provided enough info, please do let me know.
You need to place your if statement in the Foreach-Object loop. This will also only work, if your variable all get the same name of $RegKey. To incriment, you may use the for loop.
Import-Csv -Path C:\something\mylist.csv | ForEach-Object {
New-Variable -Name $_.Name -Value $_.Value -Force
IF (Test-Path $RegKey1) {
Write-Output "It worked"
}
else {
Write-Output "It didn't work"
}
}
The if statement returns a boolean value of $true, or $false. So theres no need to use $LastExitCode by placing the Test-Path as the condition to evaluate for.
Alternatively, you can use the Foreach loop to accomplish the same thing here:
$CSV = Import-Csv -Path C:\something\mylist.csv
Foreach($Key in $CSV.Value){
$PathTest = Test-Path -Path $Key
if($PathTest) {
Write-Output "It worked"
} else {
Write-Output "It didn't work"
}
}
By iterating(reading through the list 1 at a time) through the csv only selecting the value(Reg Path), we can test against that value by assigning its value to the $PathTest Variable, to be evaluated in your if statement just like above; theres also no need to assign it to a variable and we can just use the Test-Path in your if statement like we did above as well for the same results.
I understand the Theories but need a little help in practice when it comes to testing.
So I'm still in the process of learning testing in general and after some research I can see that the code coverage is generated by how much each line of code is tested. I'm also learning c# sharp side by side and improving my scripting skills as much as possible in the process. I also understand that you usually want to mock out any dependencies when it comes to unit testing a method that relies on another method or class. In this case it's a function or cmdlet since it's powershell.(Learning these side by side shows how much more I can do in c#)
All I'm asking for is a little help identifying things that need to be mocked and how many tests should I write? I know that sounds subjective but I'm trying to get a general idea and best practice for myself. I keep hearing from my co-workers that TDD is the way to go and it does speed up the development/scripting process significantly by catching bugs early that you're going to run in to anyway but don't have to test manually. I'm trying to learn to do tests first but need a little bit of guidance. I apologize if I seem like a scrub. I just need validation that I'm learning correctly.
To make my questions clearer:
Am I identifying what I need to mock correctly?
What am I missing or could identify better?
Am I identifying what I need to test correctly?
What I'm looking for in an answer:
Here's what you're Missing/Screwed up/Could identify better
The Code I'm Testing
Function 1
function New-ServiceSnapshot {
[CmdletBinding()]
param (
# path to export to
[Parameter(Mandatory=$true)]
[string]$ServiceExportPath
)
$results = get-service
$results | Export-Csv -Path $ServiceExportPath -NoTypeInformation
}
On function 1 at first glance I look at it and can see 2 dependencies. I would want to mock out get-service and use dummy data to do this. I did it by an import-csv from real dummy data in to the real object that would be tested. In order to test the export I have no clue. I would maybe mock out the return for the input ExportPath parameter? Just return the string path that was input? Would that suffice?
Function 2
function Inspect-ServiceSnapshot {
[CmdletBinding()]
param (
#Snapshot
[Parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$SnapshotPath,
# timer
[Parameter(Mandatory=$false)]
[int]$TimeToWait = 0,
# If variable
[Parameter(Mandatory=$false)]
[object[]]
$SnapshotVariable,
# compare variable
[Parameter(Mandatory=$false)]
[object[]]
$CompareVariable
)
if($TimeToWait) { Start-Sleep -Seconds $TimeToWait }
if($SnapshotVariable){$snap = $SnapshotVariable}
if($SnapShotPath){$snap = Import-Csv -Path $SnapshotPath}
if($CompareVariable){$Compare = $CompareVariable}else{$Compare = (Get-Service)}
if($null -eq $CompareVariable -and $null -ne $SnapshotVariable){Write-Error -Message "If you have a SnapshotVariable, Compare variable is required" -ErrorAction Stop}
if($null -eq $SnapshotVariable -and $null -ne $CompareVariable){Write-Error -Message "If you have a CompareVariable, SnapshotVariable is required" -ErrorAction Stop}
$list = #()
foreach($entry in $Compare) {
# make sure we are dealing with the SAME service
$previousStatus = $snap | Where-Object { $_.Name -eq $entry.Name} | Select-Object -Property Status -first 1 -ExpandProperty Status
$info = New-Object -TypeName psobject -Property #{
Name = $entry.Name
CurrentStatus = $entry.status
DisplayName = $entry.displayname
StartType = $entry.StartType
PreviousStatus = if ($null -ne $previousStatus) { $previousStatus } else { 'Didnt Exist before, New Service' }
isDifferent = if($previousStatus -ne $entry.status) {$true} elseif($null -eq $previousStatus) {$null} else { $false}
}
$list += $info
}
$list | Where-Object { $_.isDifferent -eq $true -and $_.StartType -ne "Automatic"}
}
On this I think I would want to test all 4 of the inputs. I also would have to mock the dependencies and some of the inputs since they require real objects for dummy data. Other than that. No Clue.
Below is my Powershell script:
$server_file = 'serverlist.txt'
$servers = #{}
Get-Content $server_file | foreach-object -process {$current = $_.split(":"); $servers.add($current[0].trim(), $current[1].trim())}
foreach($server in $servers.keys){
write-host "Deploying $service on $server..." -foregroundcolor green
}
My serverlist.txt looks like this:
DRAKE : x64
SDT: x64
IMPERIUS : x64
Vwebservice2012 : x64
Every time I run this script, I get IMPERIUS as my server name. I would like loop through the servers in the order they are written in serverlist.txt.
Am I missing anything in Get-Content call?
Don't store servers in a temporary variable.
The iteration order of hashtables (#{}) is not guaranteed by the .NET framework. Avoid using them if you want to maintain input order.
Simply do:
$server_file = 'serverlist.txt'
Get-Content $server_file | ForEach-Object {
$current = $_.split(":")
$server = $current[0].trim()
$architecture = $current[1].trim()
Write-Host "Deploying $service on $server..." -ForegroundColor Green
}
Note: Even if it probably won't make much of a difference in this particular case, in general you always should explicitly define the file encoding when you use Get-Content to avoid garbled data. Get-Content does not have sophisticated auto-detection for file encodings, and the default it uses can always be wrong for your input file.
I have written a script and as part of the script I am checking if a job is running and if it is, forcing it to stop:
$copyjob = Get-Job -Name DBACopy_QFR1-DBA20_2_QFR3-DBS21_S_Drv_DBA -ErrorAction SilentlyContinue
if ($copyjob)
{
remove-job $copyjob -force # This job my be causing problems with the backups so we kill it to make sure.
}
However, I think what I have written is the cause of this error when the script runs:
Cannot convert value "System.Management.Automation.PSRemotingJob" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0
Is there a better way of doing it that doesn't throw up that error, or am I completely incorrect in thinking that this is what is spawning the error.
Instead of
if ($copyjob)
{
}
try using
if ($copyjob -ne $null)
{
}
If that is all your code does, you can simplify that:
Remove-Job -Name DBACopy_QFR1-DBA20_2_QFR3-DBS21_S_Drv_DBA -ErrorAction SilentlyContinue
Less code, less chances to fail. You can also pipeline:
Get-Job -Name "SomeName" -ErrorAction SilentlyContinue | remove-job -force
I have a script that functions the way I want it to but it's slow. I tried using the same method in a workflow with foreach parallel but the set-variable command is not something that can be used within a workflow. I wanted to see if the way I'm doing this is incorrect and if there's a better way to get what I'm doing. The reason I want to do parallel requests is because the script can take quite a long time to complete when expanded to 20+ servers as is does each server in turn where as being able to do them all in one go would be quicker.
Below is a dumbed down version of the script (that works without parallel foreach) but it's effectively what I need to get working:
$servers = #("server1", "server2");
foreach ($s in $servers) {
$counter_value = get-counter "\\$s\counter_name"
Set-Variable -name "{s}counter" -value $counter_value
write-host ${server1counter}
Commands not supported in workflows needs to be executed in an Inlinescript. Try (untested):
workflow t {
$servers = #("server1", "server2");
foreach -parallel ($s in $servers) {
inlinescript {
$counter_value = get-counter "\\$using:s\counter_name"
Set-Variable -name "$($using:s)counter" -value $counter_value
#write-host with a PerformanceCounterSampleSet isn't a good combination. You'll only get the typename since it's a complex type (multiple properties etc.)
write-host (Get-Variable "$($using:s)counter" -ValueOnly)
}
}
}
t