New-RandomData generates either pseudorandom or cryptographically secure random data text files. It can also be used to generate random passwords easily by streaming the output to STDOUT with the -StreamToSTDOUT parameter (and assigning to a variable).
#requires -version 2
<#
.SYNOPSIS
New-RandomData generates either pseudorandom or cryptographically secure random data
text files. It can also be used to generate random passwords easily by streaming the
output to STDOUT with the -StreamToSTDOUT parameter (and assigning to a variable).
Pseudorandom random data generation is faster than cryptographically strong random data
generation. About ten times faster on testing with a file of size 1 MiB.
It also supports creating "blank" files nearly instantly by using the fsutil.exe utility,
but these files have no random data, just zero bytes (`0 or \0). Use the parameter
-NoRandomData for that. This is not useful in combination with -StreamToSTDOUT. Simply use
"`0" * $Length # if you want that.
.DESCRIPTION
The default character set can be overridden with -RandomChar, or you can exclude characters
from it with -RandomCharExclude. The default character set is 64 characters long and
evenly divisible by 256, so it generates a "perfect" array of 256 elements, and giving
0 % overhead when generating cryptographically secure data.
The -Size parameter controls the total number of random generated characters, and it is
recommended that you make -LineLength a number that the specified size is evenly
divisible by. Otherwise there might be rounding errors leading to the size being off by
1-2 bytes on the remainder line / file size.
The maximum number of random characters for generating cryptographically strong data is
256 due to the System.Security.Cryptography.RNGCryptoServiceProvider random number
generator generating a random byte. I suppose I could have supported addition of
these numbers to generate larger numbers. For pseudorandom data, you can have as many
characters in the array as you want.
Online "blog" documentation here:
http://www.powershelladmin.com/wiki/Create_cryptographically_secure_and_pseudorandom_data_with_PowerShell
BSD 3-clause license.
Copyright (C) 2016, Joakim Svendsen.
All rights reserved.
Svendsen Tech
The following web resources were useful:
https://en.wikipedia.org/wiki/Pseudorandomness
http://stackoverflow.com/questions/28181000/improve-powershell-performance-to-generate-a-random-file
http://stackoverflow.com/questions/533636/generating-random-files-in-windows
https://msdn.microsoft.com/en-us/library/system.security.cryptography.rngcryptoserviceprovider%28v=vs.110%29.aspx
.PARAMETER Path
Directory to store random file in. Default is current working directory ($Pwd.Path).
USe "(Get-Location)" for current directory, not ".", because that defaults to the profile
directory inside the cmdlet.
.PARAMETER BaseName
If generating one file, it is the file name plus a 1 plus the extension. If generating
more than one file, it is the base name for the file plus a possibly zero-padded count number.
If you specify -RandomFileNameGUID, a GUID will be appended to this base name instead of
the count number, making the file name also pseudorandom. The default is "random_file-".
.PARAMETER Extension
The extension used for the generated files.
.PARAMETER Count
Number of files to generate. Default 1.
.PARAMETER Size
File size in bytes. Default 1024 B. If you specify a line length which the size is
not evenly divisible by, the size can be off by +/- 1-2 bytes.
.PARAMETER LineLength
Number of bytes per line, including \r\n if writing a file. If you specify a line length
which the specified size is not evenly divisible by, the size can be off by +/- 1-2 bytes.
The default is 128.
.PARAMETER NoClobber
Do not overwrite existing files with the same name.
.PARAMETER RandomChar
Random char array of chars to use for generating the pseudorandom or cryptographically secure
random data. Default: [A-Za-z0-9_-]. 64 characters. Fits perfectly in a byte-sized array when
repeated four times, making it as efficient as possible when generating cryptographically secure
data, while preserving equal weighting of characters.
.PARAMETER RandomCharExclude
Characters to exclude from the default set of characters (or the supplied set).
.PARAMETER NoRandomData
Use fsutil and generate a file filled with zero bytes (`0 or \0) extremely fast.
NB! No random data with this option.
.PARAMETER RandomFileNameGUID
Append a GUID to the file base name, making the file name (also) pseudorandom.
.PARAMETER Cryptography
Use a cryptographically secure random number generator to pick random data from the
char array rather than a pseudorandom number generator.
.PARAMETER StreamToSTDOUT
Do not write any files, but stream random data to STDOUT.
#>
function New-RandomData {
[CmdletBinding()]
param(
[ValidateScript({Test-Path $_})][string] $Path = $PWD.Path,
[string] $BaseName = 'random_file-',
[string] $Extension = '.txt',
[int64] $Count = 1,
[int64] $Size = 1024,
[int64] $LineLength = 128,
[switch] $NoClobber,
[char[]] $RandomChar = [char[]] ([char]'a'..[char]'z' + [char]'A'..[char]'Z' + [char]'0'..[char]'9' + @([char]'-', [char]'_')), # 64 chars
[char[]] $RandomCharExclude = @(),
[switch] $NoRandomData,
[switch] $RandomFileNameGUID,
[switch] $Cryptography,
[switch] $StreamToSTDOUT)
begin {
function Write-FileOrSTDOUT {
param([string] $Text)
if ($StreamToSTDOUT) {
$Text
}
else {
# Remove the last two random characters to make room for \r\n.
#$Text = $Text -replace '.{2}\z'
#$Text = $Text.Remove(($Text.Length - 2))
$StreamWriter.WriteLine($Text)
}
}
if ($LineLength -gt $Size) {
Write-Error -Message "Line length cannot be greater than size." -ErrorAction Stop
return
}
if ($Cryptography -and $RandomChar.Count -gt 256) {
Write-Error -Message "When using -Cryptography, the maximum -RandomChar array size is 256." -ErrorAction Stop
return
}
[int] $PadNumber = ([string] $Count).Length
if ($NoRandomData) {
if (-not (Get-Command -Name fsutil -ErrorAction SilentlyContinue)) {
Write-Error -Message "Couldn't find fsutil. Cannot continue with the -NoRandomData parameter." -ErrorAction Stop
return
}
}
# Handle exceptions.
$RandomChar = $RandomChar | Where { $RandomCharExclude -notcontains $_ }
Write-Verbose -Message "Random char array: $($RandomChar -join ', ')"
Write-Verbose -Message "Random char array size: $($RandomChar.Count)"
if ($Cryptography) {
$ErrorActionPreference = 'Stop'
try {
$RNG = [System.Security.Cryptography.RandomNumberGenerator]::Create()
}
catch {
Write-Error "Couldn't create System.Security.Cryptography.RandomNumberGenerator object: $_" -ErrorAction Stop
return
}
$ErrorActionPreference = 'Continue'
[int64] $CryptoNumberCount = 0
# Repeat (parts of) the char array to make it as close to 256 items long as possible.
# Used later to avoid generating numbers needlessly that are
# larger than the count/length of the array. Only "full copies" are made,
# so weighting is equal.
[char[]] $RandomCharFull = @()
$RepeatCount = [math]::Floor(256 / $RandomChar.Count)
if ($RepeatCount -gt 0) {
Write-Verbose -Message "Duplicating array of length $($RandomChar.Count) completely $RepeatCount times, to fit a byte-sized array as closely as possible."
foreach ($i in 1..$RepeatCount) {
$RandomCharFull += @($RandomChar)
}
Write-Verbose -Message "Random char array size after duplication: $($RandomCharFull.Count)."
if ($Cryptography) {
Write-Verbose -Message "Probability of generating an unusable cryptography number (overhead): $(((256 - $RandomCharFull.Count) / 256 * 100).ToString('N')) %."
}
}
$RandomChar = $RandomCharFull
}
else {
$RNGPseudo = New-Object -TypeName System.Random
}
[int] $RandomCharCount = $RandomChar.Count
$StringBuilder = New-Object -TypeName System.Text.StringBuilder #-ArgumentList 0, $LineLength
[int] $NumberOfLinesNeeded = [math]::Floor($Size / $LineLength)
[float] $Remainder = $Size / $LineLength - $NumberOfLinesNeeded
# If not evenly divisible, use one less byte here, but \r\n might be added and
# increase size by 1-2 bytes later if you're writing files.
[int] $RemainderBytes = [math]::Floor($Remainder * $LineLength)
if ($RemainderBytes -gt 0) {
Write-Verbose -Message "Remainder bytes (uneven division, beware) rounded to: $RemainderBytes (from $(($Remainder * $LineLength)))."
}
}
process {
foreach ($Cnt in 1..$Count) {
if (-not $RandomFileNameGUID) {
$FileName = $BaseName + ("{0:D$PadNumber}" -f $Cnt) + $Extension
}
else {
$FileName = $BaseName + [guid]::NewGuid().Guid + $Extension
}
$FileName = Join-path -Path $Path -ChildPath $FileName
if ($NoClobber) {
if (Test-Path -LiteralPath $FileName -PathType Leaf) {
Write-Warning -Message "File '$FileName' already exists. Skipping."
continue
}
}
if ($NoRandomData) {
Remove-Item -LiteralPath $FileName -ErrorAction SilentlyContinue
fsutil.exe file createnew $FileName $Size
if ($?) {
Write-Verbose -Message "Wrote '$FileName' $([datetime]::Now)"
}
else {
Write-Warning -Message "Couldn't write '$FileName': $($Error[0].ToString())"
}
continue
}
if (-not $StreamToSTDOUT) {
# Nuke it if it's there and -NoClobber isn't specified.
Remove-Item -LiteralPath $FileName -ErrorAction SilentlyContinue
$FileStream, $StreamWriter = $null, $null
$ErrorActionPreference = 'Stop'
try {
$FileStream = New-Object -TypeName System.IO.FileStream -ArgumentList $FileName, ([System.IO.FileMode]::CreateNew)
$StreamWriter = New-Object -TypeName System.IO.StreamWriter -ArgumentList $FileStream, ([System.Text.Encoding]::ASCII), $LineLength
}
catch {
Write-Warning -Message "Error creating FileStream or StreamWriter object for file name '$FileName': $_"
continue
}
}
$ErrorActionPreference = 'Continue'
# Cache this for a performance gain.
if ($NumberOfLinesNeeded -gt 0) {
# Small optimization attempt. Generating 2 random characters less per line if
# writing a file, where \r\n will occupy two characters per line.
if (-not $StreamToSTDOUT -and $LineLength -gt 2) {
$TempLineLength = $LineLength - 2
}
else {
$TempLineLength = $LineLength
}
foreach ($LineNum in 1..$NumberOfLinesNeeded) {
#[string] $Line = ''
[void] $StringBuilder.Clear()
# This cryptography approach might be pretty slow...
if ($Cryptography) {
foreach ($i in 1..$TempLineLength) {
# Fill $Byte with a random number (from 0-255), until we get
# a number small enough to index into the char array. See comments
# on what is now around line 100-110 in the begin block for the reason why I'm using
# this potentially wasteful/inefficient approach (to preserve equal weighting).
# Largely remedied for smaller arrays. Worst array size = 129...
do {
[byte[]] $Byte = 0
$RNG.GetBytes($Byte)
$CryptoNumberCount++
} while ($Byte -ge $RandomCharCount)
[void] $StringBuilder.Append(-join $RandomChar[$Byte])
}
}
else {
foreach ($i in 1..$TempLineLength) {
#-join (Get-Random -Count $LineLength -InputObject $RandomChar) # only one occurrence of each char in inputobject...
[void] $StringBuilder.Append(-join $RandomChar[$RNGPseudo.Next($RandomCharCount)])
}
}
Write-FileOrSTDOUT -Text $StringBuilder.ToString() #-StreamToSTDOUT $StreamToSTDOUT
}
}
if ($RemainderBytes) {
#Write-Verbose -Message "Remainder bytes: $RemainderBytes"
#$Line = ''
[void] $StringBuilder.Clear()
if ($Cryptography) {
foreach ($i in 1..$RemainderBytes) {
do {
[byte[]] $Byte = 0
$RNG.GetBytes($Byte)
$CryptoNumberCount++
} while ($Byte -ge $RandomCharCount)
[void] $StringBuilder.Append(-join $RandomChar[$Byte])
}
}
else {
foreach ($i in 1..$RemainderBytes) {
#-join (Get-Random -Count $LineLength -InputObject $RandomChar) # only one of each char...
[void] $StringBuilder.Append(-join $RandomChar[$RNGPseudo.Next($RandomCharCount)])
}
}
Write-FileOrSTDOUT -Text $StringBuilder.ToString() #-RemainderBytes $RemainderBytes -StreamToSTDOUT $StreamToSTDOUT
}
if (-not $StreamToSTDOUT) {
$StreamWriter.Close()
$FileStream.Close()
Write-Verbose -Message "Wrote '$FileName'. $([datetime]::Now)."
}
}
}
end {
if (Get-Variable -Name RNG -ErrorAction SilentlyContinue) {
$RNG.Dispose()
}
[gc]::Collect()
if ($Cryptography) {
Write-Verbose -Message "Had to get $CryptoNumberCount cryptography numbers."
}
}
}