Using Get-Random after using Where-Object creates a null valued expression - arrays

I'm trying to create a 10 character password that includes a mix of numbers, letters (uppercase and lowercase), and symbols.
Below is the script I am using in the function:
Function Get-TempPassword {
$TempPassword = $null
$ascii = $NULL;For ($a = 33;$a –le 126;$a++) {$ascii +=, ([char][byte]$a | Where-Object {$_ -notin "'",'`','|','_',"`;",'"',','})}
Do {$TempPassword += $ascii | Get-Random; $loop++}
Until ($loop -eq 11)
return $TempPassword
}
If I remove the following section:
| Where-Object {$_ -notin "'",'`','|','_',";",'"',','}
The creation of the password works fine albeit including the symbols I don't want included.
Having the Where-Object function causes the Get-Random function to only use the first 5 characters in the array, and therefore I don't get letters of any case type, or numbers, or any of the other symbols.
I've found that if I use $ascii[26] (being the 25th character in the array) I get a null value, however I would think this would allow any character up to this character to be used, or none at all, not just the first 5. The 25th character just so happens to be a ; (ascii value number 59). I tried adding the symbol to the Where-Object exclusion, and it was removed from the array, but the 25th character still showed as a null value.
I performed a reverse lookup of the ascii value [int[]][char[]] of each character either side of where the ; symbol would appear and it returned values 58 and 60, leading me to believe it was value 59 that was offending, but the symbol at this point should have been excluded.
Adding characters to the 'where-object' exclusion list should be removing them from the array, and it appears to, however running $ascii.Count shows 49 characters, regardless of whether I add or remove characters to the Where-Object exclusion list.
I have looked for information on the web and can't seem to find any, although it may be the search terms I'm using, as it's a bit of a complex case that not many would be reporting on.
Any help is appreciated.

I didn't write this and i can't remember where i got it but i have built this into any scripts to create random secure Windows passwords, you can specify the length of the password returned by the param [int]$PasswordLength ( i have already set it to 10 ).
function New-SWRandomPassword {
[CmdletBinding(DefaultParameterSetName='FixedLength',ConfirmImpact='None')]
[OutputType([String])]
Param
(
# Specifies minimum password length
[Parameter(Mandatory=$false,
ParameterSetName='RandomLength')]
[ValidateScript({$_ -gt 0})]
[Alias('Min')]
[int]$MinPasswordLength = 8,
# Specifies maximum password length
[Parameter(Mandatory=$false,
ParameterSetName='RandomLength')]
[ValidateScript({
if($_ -ge $MinPasswordLength){$true}
else{Throw 'Max value cannot be lesser than min value.'}})]
[Alias('Max')]
[int]$MaxPasswordLength = 11,
# Specifies a fixed password length
[Parameter(Mandatory=$false,
ParameterSetName='FixedLength')]
[ValidateRange(1,2147483647)]
[int]$PasswordLength = 10,
# Specifies an array of strings containing charactergroups from which the password will be generated.
# At least one char from each group (string) will be used.
[String[]]$InputStrings = #('abcdefghijkmnpqrstuvwxyz', 'ABCEFGHJKLMNPQRSTUVWXYZ', '23456789', '!"#%&'),
# Specifies a string containing a character group from which the first character in the password will be generated.
# Useful for systems which requires first char in password to be alphabetic.
[String] $FirstChar,
# Specifies number of passwords to generate.
[ValidateRange(1,2147483647)]
[int]$Count = 1
)
Begin {
Function Get-Seed{
# Generate a seed for randomization
$RandomBytes = New-Object -TypeName 'System.Byte[]' 4
$Random = New-Object -TypeName 'System.Security.Cryptography.RNGCryptoServiceProvider'
$Random.GetBytes($RandomBytes)
[BitConverter]::ToUInt32($RandomBytes, 0)
}
}
Process {
For($iteration = 1;$iteration -le $Count; $iteration++){
$Password = #{}
# Create char arrays containing groups of possible chars
[char[][]]$CharGroups = $InputStrings
# Create char array containing all chars
$AllChars = $CharGroups | ForEach-Object {[Char[]]$_}
# Set password length
if($PSCmdlet.ParameterSetName -eq 'RandomLength')
{
if($MinPasswordLength -eq $MaxPasswordLength) {
# If password length is set, use set length
$PasswordLength = $MinPasswordLength
}
else {
# Otherwise randomize password length
$PasswordLength = ((Get-Seed) % ($MaxPasswordLength + 1 - $MinPasswordLength)) + $MinPasswordLength
}
}
# If FirstChar is defined, randomize first char in password from that string.
if($PSBoundParameters.ContainsKey('FirstChar')){
$Password.Add(0,$FirstChar[((Get-Seed) % $FirstChar.Length)])
}
# Randomize one char from each group
Foreach($Group in $CharGroups) {
if($Password.Count -lt $PasswordLength) {
$Index = Get-Seed
While ($Password.ContainsKey($Index)){
$Index = Get-Seed
}
$Password.Add($Index,$Group[((Get-Seed) % $Group.Count)])
}
}
# Fill out with chars from $AllChars
for($i=$Password.Count;$i -lt $PasswordLength;$i++) {
$Index = Get-Seed
While ($Password.ContainsKey($Index)){
$Index = Get-Seed
}
$Password.Add($Index,$AllChars[((Get-Seed) % $AllChars.Count)])
}
Return $(-join ($Password.GetEnumerator() | Sort-Object -Property Name | Select-Object -ExpandProperty Value))
}
}
}
New-SWRandomPassword
EDIT:::
https://gallery.technet.microsoft.com/scriptcenter/Generate-a-random-and-5c879ed5
The script can be found here.

Short version (best method for me):
$possible=36..38 + 40..43 + 45..58 + 60..94 + 97..123 + 125..126 + 33
(get-random -count 10 -input $possible | % {[char]$_}) -join ''

Using the following script seems to have worked exactly how I want.
I removed the comma (,) from after += on this line:
$ascii = $NULL;For ($a = 33;$a –le 126;$a++) {$ascii +=, ([char][byte]$a | Where-Object {$_ -notin "'",'`','|','_',";",'"',','})}
I created a blank array before the array is added to:
$ascii = #()
The full code block is below:
Function Get-TempPassword {
$TempPassword = $null
$ascii = #()
For ($a = 33;$a –le 126; $a++) { $ascii += ([char][byte]$a | Where-Object { $_ -notin "'",'`','|','_',";",'"',',' }) }
Do {$TempPassword += $ascii | Get-Random; $loop++}
Until ($loop -eq 11)
return $TempPassword
}

Recursive method :
Function random-password ($length = 10)
{
$Assembly = Add-Type -AssemblyName System.Web
$password = [System.Web.Security.Membership]::GeneratePassword($length, 2)
$desablechar = "[``'|_;,`"]"
if ($password -match $desablechar )
{
random-password $length
}
else
{
$password
}
}
random-password

try Something like this :
Function random-password ($length = 10)
{
$possible=36..38 + 40..43 + 45..58 + 60..94 + 97..123 + 125..126 + 33
$password = get-random -count $length -input $possible |
% -begin { $aa = $null } -process {$aa += [char]$_} -end {$aa}
return $password
}
random-password

I have rectified my others propositions, but i propose an other method :)
Function random-password2 ($length = 10)
{
$Assembly = Add-Type -AssemblyName System.Web
$password = [System.Web.Security.Membership]::GeneratePassword(50, 2)
$possible=36..38 + 40..43 + 45..58 + 60..94 + 97..123 + 125..126 + 33
$newchar=[char](get-random -count 1 -input $possible)
$password=$password -replace "[`'|_;,]", $newchar
$password.Substring(0, $length)
}
random-password2

Related

PowerShell array getting new entries

In this snippet I have a function (FDiskScan) that gets a computer name as an input and should return an array of objects.
function FDiskScan ([String] $name)
{
$outarray = [System.Collections.ArrayList]#()
$diskscan = Get-WmiObject Win32_logicaldisk -ComputerName $name
foreach ($diskobj in $diskscan)
{
if($diskobj.VolumeName -ne $null )
{
$max = $diskobj.Size/1024/1024/1024
$free = $diskobj.FreeSpace/1024/1024/1024
$full = $max - $free
$obj = #{
'ID' = $diskobj.deviceid
'Name' = $diskobj.VolumeName
'TotalSpace' = $max
'FreeSpace' = $free
'OccupiedSpace' = $full }
$TMP = New-Object psobject -Property $obj
$outarray.Add($TMP)
}
}
return $outarray
}
$pc = "INSERT PC NAME HERE"
$diskdata = [System.Collections.ArrayList]#()
$diskdata = FDiskScan($pc)
foreach ($disk in $diskdata)
{
Write-Host "Disco: " $disk.ID
Write-Host "Espaço Total: " ([math]::Round($disk.TotalSpace, 2)) "GB"
Write-Host "Espaço Ocupado: " ([math]::Round($disk.OccupiedSpace, 2)) "GB"
Write-Host "Espaço Livre" ([math]::Round($disk.FreeSpace, 2)) "GB" "`n"
}
Within the function with debugging and going into the variables I can see that everything is alright, and when the array gets out of the function and into the script scope it adds 2 more entries.
While in debug mode it tells me that $outarry within FDiskScan has the two disks that I have on my system organised as they should be.
However on:
$diskdata = FDiskScan($pc)
It says that it has an entry of value 0 on index 0 and of value 1 on index 1, then the disks follow suit, first disk C: in index 3 and disk D in index 4.
The expected behaviour was for index 0 and 1 having disks C and D respectively not a phantom 0 and 1 entries.
When adding an object to an array list in PowerShell (i.e. $outarray.Add($TMP)) the index, the object was added at, gets returned. As you don't assign the return value to a variable the function returns a System.Array containing the indexes and the entries of the array list returned by return $outarray. That's the reason why your functions return value contains 4 elements. Furthermore your functions return value in this case is not of type System.Collections.ArrayList but of type System.Array.
To avoid that behaviour do the following.
$null = $outarray.Add($TMP);
You are seeing 0, 1 because of this line - $outarray.Add($TMP). Change it to $outarray.Add($TMP) | Out-Null. I think PowerShell is printing the index when adding to the array.

PowerShell: Sort Drive Encryption Status for Custom Output

I'm trying to get the encryption status of all drives on a Windows system and sort that list in a custom formatted output. I need this because the output is going to a Nagios server; it messes up the formatting of the standard output for Get-BitLockerVolume and is too long.
Here's what I have so far. I'm trying to sort the output in such a manner that the system drive is listed first and gives the mount point (drive letter) along with the percentage.
[array]$DriveTypes = Get-BitLockerVolume | Sort-Object VolumeType | Select-Object VolumeType
[array]$DriveMounts = Get-BitLockerVolume | Sort-Object VolumeType | Select-Object MountPoint
[array]$WDEPercent = Get-BitLockerVolume | Sort-Object VolumeType | Select-Object EncryptionPercentage
for ($i = 0; $i -lt $DriveTypes.Count; $i++) {
if ($DriveIndex -eq $DriveTypes.Count) {
$TextDriveListing = $TextDriveListing + $DriveMounts.MountPoint+" ("+$DriveTypes.VolumeType+") at "+$WDEPercent.EncryptionPercentage+"%."
}
else {
$TextDriveListing = $TextDriveListing + $DriveMounts.MountPoint+" ("+$DriveTypes.VolumeType+") at "+$WDEPercent.EncryptionPercentage+"%, "
}
if ($WDEPercent.EncryptionPercentage -lt $ReqValue) {
$NoEncryptFlag = 1
}
}
My desired output, for example, is this:
C: (OperatingSystem) at 100%, D: (Data) at 0%.
What I actually end up with is this:
C: D: (OperatingSystem Data) at 100 0%, C: D: (OperatingSystem Data) at 100 0%,
I did try something deriving from an answer to "How to sort a Multi Dimensional Array in Powershell" to test it out, commenting out my aforementioned for block and putting in:
$ListDrives | ForEach-Object {
Get-BitLockerVolume #{
MountPoint = $_[0]
EncryptionPercentage = $_[1]
}
} | Sort-Object VolumeType
Write-Host $ListDrives
That spit out this error:
Cannot index into a null array.
At C:****************.ps1:142 char:3
Get-BitLockerVolume #{
~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : NullArray
What am I doing wrong? Any suggestions?
Thanks so much in advance!
Try this:
for ($i = 0; $i -lt $DriveTypes.Count; $i++) {
if ($i -eq ($DriveTypes.Count - 1)) {
$TextDriveListing = $TextDriveListing + $DriveMounts[$i].MountPoint+" ("+$DriveTypes[$i].VolumeType+") at "+$WDEPercent[$i].EncryptionPercentage+"%."
}
else {
$TextDriveListing = $TextDriveListing + $DriveMounts[$i].MountPoint+" ("+$DriveTypes[$i].VolumeType+") at "+$WDEPercent[$i].EncryptionPercentage+"%, "
}
if ($WDEPercent[$i].EncryptionPercentage -lt $ReqValue) {
$NoEncryptFlag = 1
}
}
You weren't using the $i from your For Loop to access specific indexes in your collections (i've added [$i] to each of your collection variables to do so). You were also using a variable called $DriveIndex that was never populated and I think this needed to be comparing to $i also, however the logic was also one that would never be true because the For loop would end before it was so (so i've changed the logic to ($i -eq ($DriveTypes.Count - 1)).
Here's a tidier version that I think also gets you the same result:
$TextDriveListing = ''
$Drives = Get-BitLockerVolume | Sort-Object VolumeType | Select VolumeType,MountPoint,EncryptionPercentage
$Drives | ForEach-Object {
$TextDriveListing += "$($_.MountPoint) ($($_.VolumeType)) at $($_.EncryptionPercentage)%,"
If ($_.EncryptionPercentage -lt $ReqValue) { $NoEncryptFlag = 1 }
} -End { $TextDriveListing -Replace ',$','.' }
Uses a single variable for the three properties you wanted to access, rather than putting them in to separate variables which was unnecessary.
Uses a ForEach-Object loop to access each item (and their properties) in that collection via the special token $_.
Uses a single double quoted string for output, with the object/properties accessed via the subexpression operator $().
Puts a comma on the end of each line, but then at the End of the ForEach, uses regex to replace the comma at the end of the line (regex: $ token) with a full stop.
Both sets of code are untested, so may need tweaking.

How do I get an array of strings from a powershell pipeline?

I have a list of directory names that I want to convert to absolute paths, and strip out any invalid ones. My initial attempt at doing this was the pipeline
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
However, what I get back has type [Object[]] rather than [String[]]. I tried ensuring that the paths existed (by adding a ? { Test-Path $_ } step to the pipeline, but that didn't help.
What am I doing wrong? How do I get the directories as a list of strings? I need this so that I can concatenate the array to another array of strings, specifically
$newpath = (($env:PATH -split ';'), $paths) -join ';'
object[] is simply the default array in PowerShell, and it doesn't matter in this (and most) situations. You problem is that you're trying to join to arrays using (arr1, arr2). What this acutally does is create an array with two array objects, because , is an array construction operator. Try to join the arrays using +, like this:
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
$newpath = (($env:PATH -split ';') + $paths) -join ';'
and you could even skip the splitting, and just do
$dirs = 'dir1', 'dir2', 'dir3'
$paths = $dirs | % { Resolve-Path -ea 0 $_ } | Select -ExpandProperty Path
$newpath = "$env:PATH;$($paths -join ';')"
This seems to work:
$newpath = (#($env:PATH) + $paths) -join ';'
You don't really need to explicitly cast it as [string[]]. Powershell will figure that out from the command context and coerce the data to the proper type.

How to store outlook email body in array - Powershell?

the script below reads my outlook emails but how do I access the output. I'm new too Powershell and I'm still getting used to certain things. I just want to get the body of 10 unread outlook emails and store them in an Array called $Body.
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
#checks 10 newest messages
$inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$mBody = $_.body
#Splits the line before any previous replies are loaded
$mBodySplit = $mBody -split "From:"
#Assigns only the first message in the chain
$mBodyLeft = $mbodySplit[0]
#build a string using the –f operator
$q = "From: " + $_.SenderName + ("`n") + " Message: " + $mBodyLeft
#create the COM object and invoke the Speak() method
(New-Object -ComObject SAPI.SPVoice).Speak($q) | Out-Null
}
}
This may not be a factor here, since you're looping through only ten elements, but using += to add elements to an array is very slow.
Another approach would be to output each element within the loop, and assign the results of the loop to $body. Here's a simplified example, assuming that you want $_.body:
$body = $inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$_.body
}
}
This works because anything that is output during the loop will be assigned to $body. And it can be much faster than using +=. You can verify this for yourself. Compare the two methods of creating an array with 10,000 elements:
Measure-Command {
$arr = #()
1..10000 | % {
$arr += $_
}
}
On my system, this takes just over 14 seconds.
Measure-Command {
$arr = 1..10000 | % {
$_
}
}
On my system, this takes 0.97 seconds, which makes it over 14 times faster. Again, probably not a factor if you are just looping through 10 items, but something to keep in mind if you ever need to create larger arrays.
define $body = #(); before your loop
Then just use += to add the elements
Here's another way:
$body = $inbox.Items.Restrict('[Unread]=true') | Select-Object -First 10 -ExpandProperty Body

Powershell -- Array Casting Type Issue

and thank you in advance for taking a look.
I am having an issue in a script I wrote in Powershell. The script below is a little sloppy so please forgive me.
Basically, this script takes input from a directory of text files. Each file has a line in it like so, with the following structure:
GlobalPath, AgencyPath,SitePath,SizeofSite (in bytes)
\\servername\shared, \8055\Single\department, \sitename,524835900000
The line in question is:
# Split full path and peak usage
$CalculationBuffer = $DailyBuffer[$k].Split(",")
Which results in the following error:
Method invocation failed because [System.Char] doesn't contain a method named 'Split'.
At D:\script.ps1:387 char:52
+ $CalculationBuffer = $DailyBuffer[$k].Split <<<< (",")
+ CategoryInfo : InvalidOperation: (Split:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
So my question: Is the array casted incorrectly? Since it is reporting [System.Char] instead of [System.String]?
If the file I am inputting has two lines, it does not result in this error. If the file has only one line, it gets casted as [System.Char] instead.
:Full Script:
# Monthly Output File
[string]$monthoutfile = $ProgPath + "Billing\" + $monthdate + "\_Master_" + $monthdate + ".log"
[string]$currentmonth = $ProgPath + "Billing\" + $monthdate + "\"
# Define what type of files to look for
$files = gci $currentmonth | Where {$_.extension -eq ".log"}
# Create a datastore\dictionary for this month
$MonthDataDictionary = New-Object 'System.Collections.Generic.Dictionary[string,long]'
$MonthAvgDictionary = New-Object 'System.Collections.Generic.Dictionary[string,long]'
# Arrays
$DailyBuffer = #()
$CalculationBuffer = #()
$TempArray = #()
# Counters\Integers
[int]$Linesinday = 1
[int]$DayCounter = 1
[int]$LineCounter = 0
# Strings
[string]$DailyPath = ""
[string]$Outline = ""
# Longs
[long]$DailyPeak = 0
[long]$Value = 0
##########################################################################
# Begin Loop
# Write once...
#$CalcBuffer += "\"
foreach ($file in $files)
{
# First get content from text file and store in buffer
$DailyBuffer = Get-Content $file.pspath
# Determine how many lines are in the file, call function
$Linesinday = linecount $file.pspath
for ($k = 0; $k -lt $Linesinday; $k++ )
{
# Split full path and peak usage
$CalculationBuffer = $DailyBuffer[$k].Split(",")
# Store site path
$DailyPath = $CalculationBuffer[0] + $CalculationBuffer[1] + $CalculationBuffer[2]
# Store peak usage
$DailyPeak = $CalculationBuffer[3]
# Write to dictionary under conditions
# Check if current path is stored or "Site".
# If NOT .ContainsKey($DailyPath)
if (!($MonthDataDictionary.ContainsKey($DailyPath))) {
# Add Key
$MonthDataDictionary.Add($DailyPath, $DailyPeak)
# If it does contain a value
} elseif ($MonthDataDictionary.ContainsKey($DailyPath)) {
# Add the value to the current value for averaging
$MonthDataDictionary.Item($DailyPath) += $DailyPeak
}
}
# Accumulator
$DayCounter ++
}
# Now that each file is tallied up, run an average calculation
$MonthDataDictionary.getenumerator() | Foreach-Object -process {
$Value = $_.Value / $DayCounter
$MonthAvgDictionary.Add($_.Key, $Value)
}
# Debug:
# Write-Host the values
$MonthAvgDictionary
# Output the "Average Peak" values to a file
$MonthAvgDictionary.getenumerator() | Foreach-Object -process {
# Construct output line
$OutLine = $_.Key + "," + $_.Value
$OutLine >> $MonthOutFile
}
This is a known pitfall in Powershell. Just wrap the Get-Content in an array "expression" #() :
$DailyBuffer = #(Get-Content $file.pspath)
I think the problem you're having is that get-content doesn't return an array of strings (lines), but a single string. Thus, when you look at $dailybuffer[k], you're looking at the kth character of the string, not the kth line.
I had the same problem and only the line just like below solved this problem. For your problem it will be:
[string[]] $DailyBuffer = Get-Content $file.pspath

Resources