Let me preface by noting this is only my third day with powershell, sorry if this is common knowledge.
This is to create a randomly generated password with requirements. I've set the minimums to 2 for each value, but it doesn't always have a minimum of 2 of each when the password is generated. I think it may be because of the Get-Random -count 10 is pulling from the pool that the rest of the string has created. I’m unsure how i would force it to create a password of 10-12 characters with a minimum of each requirement specified. Can/Should i validate the password after it's been created?
$ErrorActionPreference = "silentlycontinue"
function OnApplicationLoad {
#Note: This function is not called in Projects
#Note: This function runs before the form is created
#Note: To get the script directory in the Packager use: Split-Path $hostinvocation.MyCommand.path
#Note: To get the console output in the Packager (Windows Mode) use: $ConsoleOutput (Type: System.Collections.ArrayList)
#Important: Form controls cannot be accessed in this func
else{Stop-Process -name powershell.exe}
return $true #return true for success or false for failure
}
function OnApplicationExit {
#Note: This function is not called in Projects
#Note: This function runs after the form is closed
$script:ExitCode = 0 #Set the exit code for the Packager
}
#######Static Password Resources######
$caps = [char[]] "ABCDEFGHJKMNPQRSTUVWXY"
$lows = [char[]] "abcdefghjkmnpqrstuvwxy"
$nums = [char[]] "2346789"
$spl = [char[]] "#%$+<=>?"
$ofs = ""
####################Starts code############################
function Call-test_pff {
$form1 = New-Object 'System.Windows.Forms.Form'
$form1.ClientSize = '514, 640'
$form1.Name = "form1"
$form1.Text = "Password tool"
$form1.add_Load($form1_Load)
#Add Icon to window
$Icon = [system.drawing.icon]::ExtractAssociatedIcon($PSHOME + "\powershell.exe")
$form1.icon = $Icon
#form1: Button1 "Generate password"
$button1 = New-Object system.windows.forms.button
$button1.location = "10, 25"
$button1.size = "125, 35"
$button1.text = "Generate password"
$button1.add_click({
$first = Get-Random -Minimum 2
$second = Get-Random -Minimum 2
$third = Get-Random -Minimum 2
$fourth = Get-Random -Minimum 2
$pwd = [string](#($nums | Get-Random -Count $first) + #($lows | Get-Random -Count $second) + #($caps | Get-Random -Count $third) + #($spl | Get-Random -Count $fourth) | Get-Random -Count 10)
$textbox1.text = $pwd
})
$form1.controls.add($button1)
#form1: Label4: "Password"
$label4 = New-Object system.windows.forms.label
$label4.location = "10, 475"
$label4.size = "200, 20"
$label4.text = "Password:"
$form1.controls.add($label4)
#form1: password box "Password"
$textbox1 = New-Object system.windows.forms.textbox
$textbox1.location = "10, 500"
$textbox1.size = "200, 30"
$textbox1.multiline = $true
$textbox1.readonly = $true
$form1.controls.add($textbox1)
#####################Stop Code Here#####################
#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($Form_StateCorrection_Load)
#Clean up the control events
$form1.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
return $form1.ShowDialog()
} #End Function
#Call OnApplicationLoad to initialize
if((OnApplicationLoad) -eq $true)
{
#Call the form
Call-test_pff | Out-Null
#Perform cleanup
OnApplicationExit
}
Assuming that your problem is in this code:
$first = Get-Random -Minimum 2
$second = Get-Random -Minimum 2
$third = Get-Random -Minimum 2
$fourth = Get-Random -Minimum 2
$pwd = [string](#($nums | Get-Random -Count $first) + #($lows | Get-Random -Count $second) + #($caps | Get-Random -Count $third) + #($spl | Get-Random -Count $fourth) | Get-Random -Count 10)
You have two related problems:
You're specifying no maximum for get-random, so you are getting very large numbers in most cases. As a result, $nums|get-random -count $first is always going to return the whole array (but in a random order) - you're asking for a random collection of (for example) 1000 selections from a list of fewer than 10.
Because of the above, Your trailing get-random -count 10 is picking 10 random characters from the entire search space ($caps, $lows,$lows,$spl)
So, you need to do two things:
Get a subset of each of your character types by limiting get-random to both a minimum and maximum.
Ensure that you get the correct distribution of characters in that final line. This can't be done with the current final line, because in theory you could end up with 10 upper-case characters if the random distribution fell that way
To solve the first, by limiting your counts to just the length of those source arrays:
$caps = [char[]] "ABCDEFGHJKMNPQRSTUVWXY"
$lows = [char[]] "abcdefghjkmnpqrstuvwxy"
$nums = [char[]] "2346789"
$spl = [char[]] "#%$+<=>?"
$ofs = ""
$first = Get-Random -Minimum 2 -Maximum $nums.Length;
$second = Get-Random -Minimum 2 -Maximum $lows.Length;
$third = Get-Random -Minimum 2 -Maximum $caps.Length;
$fourth = Get-Random -Minimum 2 -Maximum $spl.Length;
But this still doesn't solve the second problem, because you will be passing more than 50 characters into that final get-random -count10`, very easily excluding one or more required character types.
If you can deal with a set number of each type of character (in this case, 2 numbers, 3 upper, 3 lower, 2 special), do the following:
$caps = [char[]] "ABCDEFGHJKMNPQRSTUVWXY"
$lows = [char[]] "abcdefghjkmnpqrstuvwxy"
$nums = [char[]] "2346789"
$spl = [char[]] "#%$+<=>?"
$ofs = ""
$first = $nums | Get-Random -count 2;
$second = $caps | Get-Random -count 3;
$third = $lows | Get-Random -count 3;
$fourth = $spl | Get-Random -count 2;
$pwd = [string](#($first) + #($second) + #($third) + #($fourth) | Get-Random -Count 10)
$pwd
This picks random items in the correct quantity from each group, then randomizes the order of those characters.
Related
I have a question regarding the script.
I found a script that generates a password, but how to do it so that, for example, you can set the length of the password and probably some characters when generating, if you do not set it only after that, let it generate a default password, for some length
function Get-RandomCharacters($length, $characters) {
$random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
$private:ofs=""
return [String]$characters[$random]
}
function Scramble-String([string]$inputString){
$characterArray = $inputString.ToCharArray()
$scrambledStringArray = $characterArray | Get-Random -Count $characterArray.Length
$outputString = -join $scrambledStringArray
return $outputString
}
$password = Get-RandomCharacters -length 5 -characters 'abcdefghiklmnoprstuvwxyz'
$password += Get-RandomCharacters -length 1 -characters 'ABCDEFGHKLMNOPRSTUVWXYZ'
$password += Get-RandomCharacters -length 1 -characters '1234567890'
$password += Get-RandomCharacters -length 1 -characters '!"§$%&/()=?}][{##*+'
Write-Host $password
$password = Scramble-String $password
Write-Host $password
There are more ways than one to generate a password.
For instance this one:
function New-Password {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateRange(8, 128)]
[int]$TotalLength = 10,
[int]$Digits = 3,
[int]$Symbols = 2
)
# the number of symbols must be => 0 and <= $TotalLength
$Symbols = [math]::Max(0,[math]::Min($Symbols, $TotalLength))
# same for the number of digits
$Digits = [math]::Max(0,[math]::Min($Digits, $TotalLength))
$alphas = $TotalLength - ($Digits + $Symbols)
if ($alphas -lt 0) {
throw "Too many digits or symbols for the total password length"
}
$list = [System.Collections.Generic.List[char]]::new()
if ($Digits -gt 0) { $list.AddRange([char[]]([char[]]'0123456789' | Get-Random -Count $Digits)) }
if ($Symbols -gt 0) { $list.AddRange([char[]]([char[]]'!##$%^&*()_-+=[{]};:<>|./?~' | Get-Random -Count $Symbols)) }
if ($alphas -gt 0) { $list.AddRange([char[]]([char[]]'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' | Get-Random -Count $alphas)) }
($list | Sort-Object {Get-Random}) -join ''
}
Usage:
$password = New-Password -TotalLength 12 -Symbols 4 -Digits 2 # or without parameters to accept the defaults
Or this:
$TotalLength = 10
$password = ([char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9 | Sort-Object {Get-Random})[0..$TotalLength] -join ''
Or this:
Add-Type -AssemblyName System.Web
# the first parameter specifies the total password length.
# the second one specifies the minimum number of non-alphanumeric characters
$password = [System.Web.Security.Membership]::GeneratePassword(10, 3)
Note: Apparently you cannot use this last method in .NET Core as it does not support System.Web.dll. See this github issue
How about condensing down into a smaller function:
Function Generate-Password
{
-join ('abcdefghkmnrstuvwxyzABCDEFGHKLMNPRSTUVWXYZ23456789$%&*#'.ToCharArray() | Get-Random -Count 12) # Add characters and/or password length to suit your organisation's requirements
}
And then call whenever you need it:
$password = Generate-Password
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
replace line
function Get-RandomCharacters($length, $characters) {
with:
function Get-RandomCharacters($length = 8, $characters = 'abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890') {
it defines default length to 8 chars, and charset as abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890
I want to replace text in FieldCodes (Word Document). How can i use variables for that?
This is for a Word Doc with links to other Word Doc's (IncludeText Link). When i change the link one by one without variable it works. When i use variables for it, it doesn't.
$Desktop = [Environment]::GetFolderPath("Desktop")
$Word = New-Object -ComObject Word.Application
$Document = $Word.Documents.Open("$Desktop\Test.docx")
$Test = 147 # (Test.GetType() = Int32)
$Document.Fields(147) #Works
$Document.Fields($Test) #Works
$Document.Fields($Test).LinkFormat.SourceFullName = "" #Works
$TextLinks = $Document.Fields | Where-Object Type -eq "68" | Select -expand Index
#TextLinks contains value 147 and 149
$Test = $TextLinks[0] # is also 147 (Test.GetType() = Int32)
$Document.Fields($Test) #Doesn't work (runs indefinitely)
$Document.Fields($Test).LinkFormat.SourceFullName = "" #Doesn't work (runs indefinitely)
147..149 | Foreach { $Document.Fields($_).LinkFormat.SourceFullName } #Doesn't work (runs indefinitely)
Update:
Now it runs with $Test = [INT]$Textlinks[0]. Thanks Cindy!
But when i try a loop it hangs with te second value
$Desktop = [Environment]::GetFolderPath("Desktop")
$Word = New-Object -ComObject Word.Application
$Document = $Word.Documents.Open("$Desktop\Test.docx")
$TextLinks = $Document.Fields | Where-Object Type -eq "68" | Select -expand Index
$ItemNumber = 0
$End = $TextLinks.Count
Do {
$Item = [INT]$Textlinks[$ItemNumber]
if ($Document.Fields($Item).LinkFormat.SourceFullName -match "Test") {
$Link = $Document.Fields($Item).LinkFormat.SourceFullName -replace "Test", "TestTest"
$Document.Fields($Item).LinkFormat.SourceFullName = $Link
$Document.Fields($Item).LinkFormat.AutoUpdate = "True"
$ItemNumber += 1
}
} Until ($ItemNumber -eq $End)
$Document.Save()
$Word.Quit()
$OUT=[System.Runtime.InteropServices.Marshal]::ReleaseComObject($Word)
Update 2:
Code below runs fine but i dont understand why the code above doesn't
$Desktop = [Environment]::GetFolderPath("Desktop")
$Word = New-Object -ComObject Word.Application
$Document = $Word.Documents.Open("$Desktop\Test.docx")
$TextLinks = $Document.Fields | Where-Object Type -eq "68" | Select -expand Index
$ItemNumber = ($TextLinks.Count)-1
$End = -1
Do {
$Item = [INT]$Textlinks[$ItemNumber]
if ($Document.Fields($Item).LinkFormat.SourceFullName -match "Test") {
$Link = $Document.Fields($Item).LinkFormat.SourceFullName -replace "Test", "TestTest"
$Document.Fields($Item).LinkFormat.SourceFullName = $Link
$Document.Fields($Item).LinkFormat.AutoUpdate = "True"
$ItemNumber -= 1
}
} Until ($ItemNumber -eq $End)
$Document.Save()
$Word.Quit()
$OUT=[System.Runtime.InteropServices.Marshal]::ReleaseComObject($Word)
I have a powershell script that measures download time on some pages, however I get the error above, I am unsure what I am doing wrong
error is
Cannot bind argument to parameter 'InputObject' because it is null.
function ResponseTime($CommonName,$URL, $environment)
{
$Times = 5
$i = 0
$TotalResponseTime = 0
Write-HOst $URL
While ($i -lt $Times) {
$Request = New-Object System.Net.WebClient
$Request.UseDefaultCredentials = $true
$Start = Get-Date
Write-HOst $URL
$PageRequest = $Request.DownloadString($URL)
$TimeTaken = ((Get-Date) - $Start).TotalMilliseconds
$Request.Dispose()
$i ++
$TotalResponseTime += $TimeTaken
}
$AverageResponseTime = $TotalResponseTime / $i
Write-Host Request to $CommonName took $AverageResponseTime ms in average -ForegroundColor Green
$details = #{
Date = get-date
AverageResponseTime = $AverageResponseTime
ResponseTime = $Destination
Environment = $environment
}
$results += New-Object PSObject -Property $details
$random = Get-Random -minimum 1 -maximum 30
Start-Sleep -s $random
}
#PRODUCTION
ResponseTime -commonname 'app homepage' -URL 'https://url1' -environment 'PRODUCTION'
ResponseTime -commonname 'department homepage' -URL 'https://url2' -environment 'PRODUCTION'
$results | export-csv -Path c:\so.csv -NoTypeInformation
Reviewing your last edit, it seems that $results simply returns $null (As your error says)
The only line setting $results is $results += New-Object PSObject -Property $details
It is not in the scope of your Export-CSV call and - even if it would, $results could be empty, if this line is not called.
You should IMHO set it to e.g. an ArrayList like follows:
$results = New-Object -TypeName System.Collections.ArrayList
And add items to it via
$times = ResponseTime -commonname '' #etc
$results.Add($times) | Out-Null
This gives you an ArrayList - even if there are no items in it - which can easily be transformed to CSV and other formats.
#Clijsters has given the correct answer; i.e. the issue being the scope of your $results variable.
This answer just provides a bit of a code review to help you with other bits going forwards...
function Get-ResponseTime {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$CommonName
,
[Parameter(Mandatory = $true)]
[string]$URL
,
[Parameter(Mandatory = $true)]
[string]$Environment
,
[Parameter(Mandatory = $false)]
[int]$Times = 5
)
[System.Int64]$TotalResponseTime = 0
[System.Diagnostics.Stopwatch]$stopwatch = New-Object 'System.Diagnostics.Stopwatch'
Write-Verbose "Processing URL: $URL"
1..$times | foreach-object {
[System.Net.WebClient]$Request = New-Object 'System.Net.WebClient'
$Request.UseDefaultCredentials = $true
Write-Verboset "Call $_ to URL: $URL"
$stopwatch.Restart()
$PageRequest = $Request.DownloadString($URL)
$stopwatch.Stop()
$TimeTaken = $stopwatch.Elapsed.TotalMilliseconds
$Request.Dispose()
$TotalResponseTime += $TimeTaken
}
$AverageResponseTime = $TotalResponseTime / $Times
Write-Verbose "Request to $CommonName took $AverageResponseTime ms on average"
$details = #{
Date = get-date
AverageResponseTime = $AverageResponseTime
#ResponseTime = $Destination #this is not declared anywhere / don't know what this field's for
Environment = $environment
}
Write-Output (New-Object 'PSObject' -Property $details)
#do you really want a delay here? Doesn't make much sense... may make sense to include a delay in the above loop; i.e. to stagger your tests?
#$random = Get-Random -minimum 1 -maximum 30
#Start-Sleep -s $random
}
#PRODUCTION
[PSObject[]]$results = #(
(Get-ResponseTime -commonname 'app homepage' -URL 'https://url1' -environment 'PRODUCTION' -Verbose)
,(Get-ResponseTime -commonname 'department homepage' -URL 'https://url2' -environment 'PRODUCTION' -Verbose)
)
$results | Export-Csv -LiteralPath 'c:\so.csv' -NoTypeInformation
Use verb-noun function names (e.g. Get-Item). What is the naming convention for Powershell functions with regard to upper/lower case usage?
Use "Cmdlets" (Advanced Functions) instead of (Basic) Functions; they're basically the same thing, only tagged with [Cmdletbinding()]. The reason for this you get support for functionality such as verbose output. http://www.lazywinadmin.com/2015/03/standard-and-advanced-powershell.html
Use a stopwatch to time processes (you could also use measure-command; but any output would be suppressed / consumed by the measure-command function). Timing a command's execution in PowerShell
Have your cmdlet output its values to the pipeline via Write-Output (or you can leave off the function name; any output caused by placing a variable with nothing to process it will be fed to the pipeline; i.e. write-object $a is the same as a line solely consisting of $a).
Capture the output into your $results variable outside of the function, and handle the results there.
I'm writing a powershell script that searches for users inside an Active Directory OU and allows me to reset passwords by choosing matches from a list. I found a Tutorial that uses the System.DirectoryServices.DirectoryEntry and System.DirectoryServices.DirectorySearcher, and modified it like so:
$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP:\\[REDACTED]")
##ReadSTDIN
$strSearch = Read-Host -Prompt "Search"
$strCat = "(&(objectCategory=User)(Name=*" + $strSearch + "*))"
## Search Object
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strCat
$objSearcher.SearchScope = "Subtree"
#Load Required Properties into the dynObjLink
$objSearcher.PropertiesToLoad.Add("name")
$objSearcher.PropertiesToLoad.Add("userPrincipalName")
$objSearcher.PropertiesToLoad.Add("SamAccountName")
##Magical Search Function
$colResults = $objSearcher.FindAll()
$colResults.PropertiesLoaded
#for every returned userID add them to a table
ForEach ($objResult in $colResults)
{$a++
$objResult.count
$objItem = $objResult.Properties
$objItem.name
$objItem.userPrincipalName
$results.Add($a, $objItem.name + $objItem.userPrincipalName + $objItem.SamAccountName)
}
#Print Table
$results | Format-Table -AutoSize
This works well enough, but when it prints data I can only get the "first name" value of anything that comes back. Everything else becomes NULL and I can't figure out why.
Name Value
---- -----
3 {James3 [REDACTED], $null, $null}
2 {James2 [REDACTED], $null, $null}
1 {James1 [REDACTED], $null, $null}
I've tried different kinds of authentication and manipulating values, but the DirectorySearcher object only seems to collect the "name" value of any record it returns, no matter what I load into it. Help?
Here's a bit shorter (and PowerShell v2-compatible) way of doing this:
#requires -version 2
param(
[Parameter(Mandatory=$true)]
[String] $SearchPattern
)
$searcher = [ADSISearcher] "(&(objectClass=user)(name=$SearchPattern))"
$searcher.PageSize = 1000
$searcher.PropertiesToLoad.AddRange(#("name","samAccountName","userPrincipalName"))
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$properties = $searchResult.Properties
$searchResult | Select-Object `
#{Name = "name"; Expression = {$properties["name"][0]}},
#{Name = "sAMAccountName"; Expression = {$properties["samaccountname"][0]}},
#{Name = "userPrincipalName"; Expression = {$properties["userprincipalname"][0]}}
}
}
$searchResults.Dispose()
Note that there's no need to build a list and output afterwards. Just output each search result. Put this code in a script file and call it:
PS C:\Scripts> .\Searcher.ps1 "*dyer*"
If you omit the parameter, PowerShell will prompt you for it (because the parameter is marked as mandatory).
try using Properties matching to the PropertiesToLoad
$entry = new-object -typename system.directoryservices.directoryentry -ArgumentList $LDAPServer, "ldap", "esildap"
$entry.Path="LDAP://OU=childOU,OU=parentOU,DC=dc1,DC=dc2"
$searcher = new-object -typename system.directoryservices.directorysearcher -ArgumentList $entry
$searcher.PropertiesToLoad.Add('samaccountname')
$searcher.PropertiesToLoad.Add('mail')
$searcher.PropertiesToLoad.Add('displayname')
$objs = $searcher.findall()
foreach($data in $objs)
{
$samaccountname = $data.properties['samaccountname'][0] + ''
$mail = $data.properties['mail'][0] + ''
$displayname = $data.properties['displayname'][0] + ''
}
when accessing the properties of the resultset you get a System.DirectoryServices.ResultPropertyValueCollection type for each property
to get a string value for passing to a database the property value access the zero index of the object
I'm trying to create a script to make an hardware inventory of my PCs and exporting it within excel.
I have some problem listing hard drive.
Let's suppose one PC has two partions, C with 100 GB and E with 200 GB. I'd like to put drives within a single cell in this way with a carriage return.
C: 100 GB
E: 200 GB
If I want to create an excel file I can do something like this
$a = New-Object -comobject Excel.Application
$a.Visible = $True
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$c.Cells.Item(1,1) = "A value in cell A1."
and I know I can query wmi to list hard drive:
gwmi win32_logicaldisk | ? {$_.drivetype -eq 3} | select deviceid,#{Label="Disk GB"; Expression={[math]::truncate($_.Size / 1GB)}}
but I don't know how to get my desired output. Thanks in advance.
http://www.java2s.com/Code/VBA-Excel-Access-Word/File-Path/GetDriveInformation.htm answers you question
How does this work?
$a = New-Object -comobject Excel.Application
$a.Visible = $True
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$label=gwmi win32_logicaldisk | select deviceid,#{Label="Disk GB"; Expression={"$([math]::truncate($_.Size / 1GB)) GB"}} | ft -auto -HideTableHeaders | out-string
$c.Cells.Item(1,1) = $label
You can iterate over the returned drives from your WMI query and add the deviceid and "Disk GB" results to a string value. Then you can write the resulting string value to the Excel cell. One way to do this would be like this:
$a = New-Object -comobject Excel.Application
$a.Visible = $True
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$outputstring = ""
foreach ($drive in $driveinfo) {
$outputstring += "$($drive.deviceid) $($drive."Disk GB") GB`n"
}
$c.cells.item(1,1) = $outputstring