I have a PowerShell script (see below) that locates any duplicate files found outside of the input path, and if any are found, e-mails an attachment with the information.
It worked on my personal machine, and I am currently testing it on our server. I never expected it to be fast, but I am currently an hour in to the test and it still is not finished!
My question is then, is there anything I can do to reduce the time it takes to run?
As an additional question: I encrypted the password file via PowerShell... is it possible for someone who has access to the file to decrypt it and view the password in plain text?
Any help would be appreciated!
$sourcepath = "\\server1\privatetest\"
$duplicatepath = "\\server1\public\"
$dup_found = 0
function Send-ToEmail([string]$email, [string]$attachmentpath){
$message = new-object Net.Mail.MailMessage;
$message.From = "MyEmail#MyDomain.com";
$message.To.Add($email);
$message.Subject = "Duplicate Found";
$message.Body = "Please see attachment";
$attachment = New-Object Net.Mail.Attachment($attachmentpath);
$message.Attachments.Add($attachment);
$smtp = new-object Net.Mail.SmtpClient("smtp.gmail.com", "587");
$smtp.EnableSSL = $true;
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message);
$attachment.Dispose();
}
If ((Test-Path $sourcepath) -AND (Test-Path $duplicatepath)) {
$sourcefiles = Get-ChildItem $sourcepath -File -Recurse -ErrorAction SilentlyContinue | Get-FileHash
$dupfiles = Get-ChildItem $duplicatepath -File -Recurse -ErrorAction SilentlyContinue | Get-FileHash
$duplicates = [System.Collections.ArrayList]#()
If (($sourcefiles.count -eq 0) -or ($dupfiles.count -eq 0)) {
If ($sourcefiles.count -eq 0) {
Write-Warning 'No files found in source path'
}
else {
Write-Warning 'No files found in duplicate path'
}
Break
}
else {
foreach ($sf in $sourcefiles) {
$result1path = $sf | Select -Property Path
$result1hash = $sf | Select -Property Hash
foreach ($df in $dupfiles) {
$result2path = $df | Select -Property Path
$result2hash = $df | Select -Property Hash
If (($result1hash) -like ($result2hash)) {
$dup_found = 1
$dupmsg = 'Source Path: '
$dupmsg = $dupmsg + $result1path
$dupmsg = $dupmsg + ', Source Hash: '
$dupmsg = $dupmsg + $result1hash
$dupmsg = $dupmsg + ', Duplicate Path: '
$dupmsg = $dupmsg + $result2path
$dupmsg = $dupmsg + ', Duplicate Hash: '
$dupmsg = $dupmsg + $result2hash
$duplicates = $duplicates + $dupmsg
}
}
}
if ($dup_found -eq 1) {
$Username = "MyEmail#MyDomain.com";
$pwfile = Get-Content "PasswordFile"
$Password = $pwfile | ConvertTo-SecureString
$path = "C:\temp\duplicates.txt";
$duplicates | Out-File -FilePath C:\temp\duplicates.txt
Send-ToEmail -email "MyEmail#MyDomain.com" -attachmentpath $path;
Remove-Item C:\temp\duplicates.txt
}
}
}
else {
If(!(Test-Path $sourcepath)) {
Write-Warning 'Source path not found'
}
elseif(!(Test-Path $duplicatepath)) {
Write-Warning 'Duplicate path not found'
}
}
[...], is there anything I can do to reduce the time it takes to run?
Yes, there most certainly is!
Reduce the runtime complexity
What you have here is a classic performance-gotcha - by comparing each file in one collection to every other file in the other, you've created a quadratic algorithm.
What does quadratic mean? It means that for N input items in each collection, you now have to perform N^2 comparisons - so if each directory contains 1 file, you only need one comparison - but with 2 files, you need 4 comparisons, 3 files = 9 comparisons, etc. - already at just 100 files in each directory, you'll need to make 10.000(!) comparisons.
Instead, you'll want to use a data structure that's fast at determining whether a specific value is contained within it or not. For this purpose, you could use a hash table:
# Create a hashtable
$sourceFileIndex = #{}
# Use source files to populate the hashtable - we'll use the calculate hash as the key
$sourcefiles = Get-ChildItem $sourcepath -File -Recurse -ErrorAction SilentlyContinue |ForEach-Object {
$hashed = $_ |Get-FileHash
$sourceFileIndex[$hashed.Hash] = $hashed
}
# Keep the potential duplicates in an array, no need to change anything here
$dupfiles = Get-ChildItem $duplicatepath -File -Recurse -ErrorAction SilentlyContinue | Get-FileHash
#...
# Now we can remove the outer loop completely
foreach ($df in $dupfiles) {
# Here's the magic - replace the string comparison with a call to ContainsKey()
if ($sourceFileIndex.ContainsKey($df.Hash)) {
$dup_found = 1
$dupmsg = 'Source Path: '
$dupmsg = $dupmsg + $result1path
$dupmsg = $dupmsg + ', Source Hash: '
$dupmsg = $dupmsg + $result1hash
$dupmsg = $dupmsg + ', Duplicate Path: '
$dupmsg = $dupmsg + $result2path
$dupmsg = $dupmsg + ', Duplicate Hash: '
$dupmsg = $dupmsg + $result2hash
$duplicates = $duplicates + $dupmsg
}
}
This should already give you a massive performance boost.
Reduce string manipulation to a minimum
Another costly aspect of your current approach (although not as significant as the problem described above) is the constant string concatenation - the runtime needs to re-allocate memory for all the individual little substrings and this can eventually take a toll on execution time when processing high volumes of data.
One way to reduce string manipulation is by creating structured objects instead of maintaining a running "output string":
foreach ($df in $dupfiles) {
# Here's the magic - replace the string comparison with a call to ContainsKey()
if ($sourceFileIndex.ContainsKey($df.Hash)) {
$dup_found = 1
# Create output object
$dupeRecord = [pscustomobject]#{
SourcePath = $sourceFileIndex[$df.Hash].Path
SourceHash = $df.Hash # these are identical, no need to fetch the "source hash"
DuplicatePath = $df.Path
DuplicateHash = $df.Hash
}
[void]$duplicates.Add($dupeRecord)
}
}
This brings about another improvement! Since these are objects (as opposed to raw strings), you now have greater choice/flexibility when it comes to output formatting:
# Want an HTML table? Go ahead!
$duplicates |ConvertTo-Html -As Table |Out-File .\path\to\attachment.html
Related
What I do, is copying photo files from SD card to HDD using powershell ps1 file and Windows PowerShell ISE.
I get a taken date from image exif and add it to destination path.
The problem is that robocopy creates folders and adds strange prefix, which I do not want to have.
As a result I can see two subfolders with same name "2020", one folder created by hand and the other created by robocopy.
This prefix is only seen when I list folders with CMD.
The prefix not seen in output.log and in powershell.
$copy_from = "G:\DCIM\100MSDCF\"
$copy_to = "C:\Photos\"
function GetDateTaken {
param (
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName')]
[String]
$Path
)
begin {
$shell = New-Object -COMObject Shell.Application
}
process {
$returnvalue = 1 | Select-Object -Property Name, DateTaken, Folder
$returnvalue.Name = Split-Path $path -Leaf
$returnvalue.Folder = Split-Path $path
$shellfolder = $shell.Namespace($returnvalue.Folder)
$shellfile = $shellfolder.ParseName($returnvalue.Name)
$returnvalue.DateTaken = $shellfolder.GetDetailsOf($shellfile, 12)
$returnvalue.DateTaken
}
}
$file = Get-ChildItem -Path $copy_from -recurse -include ('*.jpg','*.arw')
$i = 0
$jpg = 0
$arw = 0
$logifile = 'output.log'
if ([System.IO.File]::Exists($logifile)) {
Clear-Content $logifile
Write-Host ("Logfile cleaned: $logifile")
} else {
try {
New-Item -Path . -Name $logifile | Out-Null
Write-Host ("New logfile created: $logifile")
}
catch {
"Failed to create $logifile"
}
}
foreach ($file in $file) {
if ($file.extension -eq '.JPG') { $jpg++ }
if ($file.extension -eq '.ARW') { $arw++ }
$i++
$datetaken = ($file.fullname | GetDateTaken).Split(' ')[0]
$datetaken_Day = $datetaken.Split('.')[0]
$datetaken_Month = $datetaken.Split('.')[1]
$datetaken_Year = $datetaken.Split('.')[2]
$TargetPath = "$copy_to$datetaken_Year\$datetaken_Month\$datetaken_Day\"
Write-Host ("$i. " + $file.Name + " `tDate taken: " + $datetaken)
robocopy $copy_from $TargetPath $file.Name /ts /fp /v /np /unilog+:$logifile | Out-Null
}
Write-Host ("`nTotal: " + $i + " files (" + $jpg + " JPG files, " + $arw + " ARW files)")
Not helps if write $TargetPath = $copy_to + $datetaken_Year + "\" + $datetaken_Month + "\" + $datetaken_Day + "\".
Not helps if I set /fat option to robocopy.
But, for example, when I set a year manualy, everything is ok $datetaken_Year = 2020
What should be fixed to create correct folder names?
Using the GetDetailsOf() method from the COM object returns localized results, which leads to your function on my Dutch machine returning the date in 'dd-MM-yyyy HH:mm' format (with invisible characters surrounding it).
A better approach IMO would be to get the date taken using System.Drawing.Imaging.Metafile to read the exif data as null-terminated byte array and parse the date from that as DateTime object using below function:
function Get-ExifDate {
# returns the 'DateTimeOriginal' property from the Exif metadata in an image file if possible
[CmdletBinding(DefaultParameterSetName = 'ByName')]
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'ByName')]
[Alias('FullName', 'FileName')]
[ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
[string]$Path,
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'ByObject')]
[System.IO.FileInfo]$FileObject
)
Begin {
Add-Type -AssemblyName 'System.Drawing'
}
Process {
# the function received a path, not a file object
if ($PSCmdlet.ParameterSetName -eq 'ByName') {
$FileObject = Get-Item -Path $Path -Force -ErrorAction SilentlyContinue
}
# Parameters for FileStream: Open/Read/SequentialScan
$streamArgs = #(
$FileObject.FullName
[System.IO.FileMode]::Open
[System.IO.FileAccess]::Read
[System.IO.FileShare]::Read
1024, # Buffer size
[System.IO.FileOptions]::SequentialScan
)
try {
$stream = New-Object System.IO.FileStream -ArgumentList $streamArgs
$metaData = [System.Drawing.Imaging.Metafile]::FromStream($stream)
# get the 'DateTimeOriginal' property (ID = 36867) from the metadata
# Tag Dec TagId Hex TagName Writable Group Notes
# ------- --------- ------- -------- ----- -----
# 36867 0x9003 DateTimeOriginal string ExifIFD (date/time when original image was taken)
# get the date taken as an array of bytes
$exifDateBytes = $metaData.GetPropertyItem(36867).Value
# transform to string, but beware that this string is Null terminated, so cut off the trailing 0 character
$exifDateString = [System.Text.Encoding]::ASCII.GetString($exifDateBytes).TrimEnd("`0")
# return the parsed date
return [datetime]::ParseExact($exifDateString, "yyyy:MM:dd HH:mm:ss", $null)
}
catch{
Write-Warning -Message "Could not read Exif data from '$($FileObject.FullName)'"
}
finally {
If ($metaData) {$metaData.Dispose()}
If ($stream) {$stream.Close()}
}
}
}
Another option would be to download and unzip ExifTool
(you can download the zip files from here)
Then use it like:
$exifTool = 'Path\To\Unzipped\ExifTool.exe' # don't forget to 'Unblock' after downloading
$file = 'Path\To\The\ImageFile' # fullname
# retrieve all date tags in the file
# -s2 (or -s -s) return short tag name add the colon directly after that
$allDates = & $exifTool -time:all -s2 $file
# try to find a line with tag 'DateTimeOriginal', 'CreateDate' or 'ModifyDate'
# which will show a date format of 'yyyy:MM:dd HH:mm:ss'
# and parse a DateTime object out of this string
$dateTaken = switch -Regex ($allDates) {
'^(?:DateTimeOriginal|CreateDate|ModifyDate):\s(\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2})' {
[datetime]::ParseExact($matches[1], 'yyyy:MM:dd HH:mm:ss', $null)
break
}
}
Short explanation of what the above returns
Both methods return the date the image was taken as a DateTime object, not a string.
This object has properties like .Year, .Month, .Day etc. It also has various methods like .AddDays(), .ToShortDateString(), .ToString() and a lot more.
If you do $datetaken = ($datetaken -split ' ')[0] as per your comment, you are asking PowerShell to implicitely convert it to a string using the default ToString() method.
You can use that ToString() method in your code if you give it the formatting string you need in between the brackets, anyway you like.
If you for instance do $dateTaken.ToString('yyyy\\MM\\dd'), you'll get a string 2020\10\08 if $dateTaken was today, which could serve as part of a file path.
In your code, you could do:
$TargetPath = Join-Path -Path $copy_to -ChildPath $dateTaken.ToString('yyyy\\MM\\dd')
# if that path does not exist yet, create it
if (!(Test-Path -Path $TargetPath -PathType Container)) {
$null = New-Item -Path $TargetPath -ItemType Directory
}
Then go ahead and copy the file to the now existing $TargetPath
Please have a look at all the standard format strings and custom format specifiers you can use on a DateTime object.
Background: I've been using Netwrix to audit permissions to network shares for a few years now and It's only ever worked smoothly 1 time..... So I've decided to move on to just an automated powershell script. I've run into a block. When I try to parse out the group members, it doesn't like the network name in front of the group name (TBANK). Then I also need to take the next step of just showing the name instead of the whole output of get-adgroupmember. Any help would be greatly appreciated as I'm very to to scripting with powershell. Current script below:
$OutFile = "C:\users\user1\Desktop\test.csv" # Insert folder path where you want to save your file and its name
$Header = "Folder Path,IdentityReference, Members,AccessControlType,IsInherited,InheritanceFlags,PropagationFlags"
$FileExist = Test-Path $OutFile
If ($FileExist -eq $True) {Del $OutFile}
Add-Content -Value $Header -Path $OutFile
$Folder = "\\server1.tbank.com\share1"
$ACLs = get-acl $Folder | ForEach-Object { $_.Access }
Foreach ($ACL in $ACLs){
$ID = $ACL.IdentityReference
$ID = $ID -replace 'TBANK\' , ''
$ACType = $ACL.AccessControlType
$ACInher = $ACL.IsInherited
$ACInherFlags = $ACL.InheritanceFlags
$ACProp = $ACL.PropagationFlags
$Members = get-adgroupmember $ID.
$OutInfo = $Folder + "," + $ID + "," + $Members + "," + $ACType + "," + $ACInher + "," + $ACInherFlags + "," + $ACProp
Add-Content -Value $OutInfo -Path $OutFile
}
First of all, there is a way better way to output a CSV file than by trying to write each row yourself (with the risk of missing out required quotes), called Export-Csv.
To use that cmdlet, you wil need to create an array of objects which is not hard to do.
$OutFile = "C:\users\user1\Desktop\test.csv" # Insert folder path where you want to save your file and its name
$Folder = "\\server1.tbank.com\share1"
# get the Acl.Access for the folder, loop through and collect PSObjects in variable $result
$result = (Get-Acl -Path $Folder).Access | ForEach-Object {
# -replace uses regex, so you need to anchor to the beginning of
# the string with '^' and escape the backslash by doubling it
$id = $_.IdentityReference -replace '^TBANK\\' # remove the starting string "TBANK\"
# Get-ADGroupMember can return users, groups, and computers. If you only want users, do this:
# $members = (Get-ADGroupMember -Identity $id | Where-Object { $_.objectClass -eq 'user'}).name -join ', '
$members = (Get-ADGroupMember -Identity $id).name -join ', '
# output an onbject with all properties you need
[PsCustomObject]#{
'Folder Path' = $Folder
'IdentityReference' = $id
'Members' = $members
'AccessControlType' = $_.AccessControlType
'IsInherited' = $_.IsInherited
'InheritanceFlags' = $_.InheritanceFlags -join ', '
'PropagationFlags' = $_.PropagationFlags -join ', '
}
}
# output on screen
$result | Format-List
# output to CSV file
$result | Export-Csv -Path $OutFile -Force -UseCulture -NoTypeInformation
I've added a lot of inline comments to hopefully make things clear for you.
The -UseCulture switch in the Export-Csv line makes sure the field delimiter that is used matches what is set in your system as list separator. This helps when opening the csv file in Excel.
P.S> the Get-ADGroupMember cmdlet also has a switch called -Recursive. With that, it will also get the members from groups inside groups
So I have a parser that goes through two different logs, both .csv files, and checks for certain lines based off the regex code that I have chosen.
This one grabs the IDNumber from the beginning of the filename(1234-randomfile.csv), then adds the files location to a variable($Validate), then based on the regex, adds files to certain variables($Scriptdone, $Updatedone, $Failed) and starts the checks to see if they have them.
I am trying to make it so that the output is not line for line as the files I parse through have the same IDNumbers. So for example:
Output Currently:
1234 Script Completed
1234 Update Completed
How I want output:
1234 Script Completed Update Completed
Anyways, Thanks for all the assistance!
function Get-MR4RES {
[CmdletBinding()]
param (
[Parameter(Position = 0,
Mandatory = $True)]
[ValidateNotNullorEmpty()]
[ValidateScript( {Test-Path -Path $_ -PathType 'Any'})]
[String]
$Files,
[Parameter(Position = 1,
Mandatory = $false)]
[String]
$CSVPath) # End Param
begin {
# Setting Global Variables
$Scriptcompletedsuccess = '.+Script\scompleted\ssuccessfully.+' # 3:44:15 End function called, Script completed successfully at 3:44:15 on Tue 07/03/2018
$Updatecomplete = '\w+\s+\:\s\[\d+\:\d+\:\d+\]\s+\w+\scomplete' # STATUS : [03:43:07] Update complete
$FailedValidaton = '.+check\sfail.+'
$Fail1 = 'Validation Failed'
$Fail2 = 'Failed'
$Good1 = 'Script completed'
$Good2 = 'Update completed'
$array = #('IDNumber, Results')
$counter = 0
$FileList = (Get-ChildItem -Path $Files -File -Filter "*.log").FullName
$Done = ''
} # End begin
process {
# Do the following code in all the files in the filelist
foreach ($File in $fileList) {
# Test files variables to ensure is directory to ensure progress bar will be operational and needed
if ((Get-Item $Files) -is [System.IO.DirectoryInfo]) {
# Counts once per each file variable in filelist variable
$counter++
# Progress bar indicates the name of the current file and calculates percent based on current count verses total files in $filelist
Write-Progress -Activity 'Analyzing Files' -CurrentOperation $File -PercentComplete (($counter / $FileList.count) * 100)
}
# Calculates ID number based on filename, file name is -filtered in beginning to only contain properly named files
$IDNumber = [System.IO.Path]::GetFileName("$File").split('-')[0]
# Puts file into Variable to be IF Else
$Validate = Get-Content -Path $File
$Scriptdone = $Validate | Where-Object {$_ -match $Scriptcompletedsuccess}
$Updatedone = $Validate | where-object {$_ -match $Updatecomplete}
$Failed = $Validate | Where-Object {$_ -match $FailedValidaton}
# Check if the file HAS a FAILED validation
if($Failed){
# Creates an array of the data from each file that failed
$array += -join ("$IDNumber",', ',"$Fail1")
}
Elseif($Scriptdone){
$Done = $Good1
# Creates an array of the data from each file that script completed
$array += -join ("$IDNumber",', ',"$Done")
} # if the parser found "Update complete"
Elseif($Updatedone){
$Done = $Good2
# Creates an array of the data from each file that update is done
$array += -join ("$IDNumber",', ',"$Done")
} # End of Successful
Else{
# Creates an array of the data from each file that failed
$array += -join ("$IDNumber",', ',"$Fail2")
}
} # End of foreach
} # End process section
End {
# If CSVPath is used in get-command
if ($PSBoundParameters.ContainsKey('CSVPath')) {
# Pipe the array data to a CSV
Add-Content -Path $CSVPath -Value $array -Encoding ascii
}
# If no CSVPath is used in get-command
else {
# Out-put to console
Write-Output $array
} # End of else
} # End of the End
} # End of function
If you want to append new message to existing output you have to tell PowerShell to which entry it should add new info. As manipulating strings is not very intuitive in my opinion I'd suggest to use an object for that.
First you have to define data structure:
// Before ForEach
$array = #()
$properties = #{'ID'="";
'Results'=""}
// In ForEach
$object = New-Object –TypeName PSObject –Prop $properties
$object.ID = $IDNumber
Next, in your if you can set the value (this can also be done using Switch as suggested by #LotPings but let's leave it as it is for simplicity):
$object.Results = $Done // or $Fail or $Fail2
Then you should first check if the entry with such $ID already exists and if yes, add new result. If no, just add new element to the array. Something like this should work:
$line = $array | Where-Object ID -eq $object.id
if ($line) {
$line.Results += " $($object.Results)"
}
else {
$array += $object
}
Of course this will also require changing the way as you output you data (for example by using Export-Csv):
$array | Export-Csv $CSVPath -Append -NoTypeInformation
I have slopped together bits of PowerShell to remote query a list of machines, stored in a .csv file, for a registry value. If the registry key's value is equal to '1', the script should then create a text file using the machine's name as the name of the text file.
Everything works great. The script runs happily without any errors. The problem is that when I go back and remotely check a targeted registry value, I find that the value isn't 1. The script is simply creating a file for every line in the .csv.
What am I doing wrong?
EDIT*** I found a problem I had a typo in the $key variable for the registry path. 7/17/2013 2:21p
$File = Import-Csv 'c:\temp\machines.csv'
foreach ($line in $file)
{
$machinename = $line.machinename
trap [Exception] {continue}
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$MachineName)
$key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon"
$regkey = ""
$regkey = $reg.opensubkey($key)
$keyValue = ""
$keyValue = $regKey.GetValue('AutoAdminLogon')
if ($keyValue = "1")
{
try
{
$textFile = New-Item -Path "c:\temp\autologin" -Name $MachineName -ItemType "File"
}
catch
{
$msg = $_
$msg
}
}
$Results = $MachineName , $keyValue
Write-host $Results
#Output Below Here:
}
In PowerShell = is an assignment operator, not a comparison operator. Change this line:
if ($keyValue = "1")
into this:
if ($keyValue -eq "1")
For more information see Get-Help about_Operators.
You're making this way too complicated, BTW. Something like this should suffice:
$keyname = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon'
Import-Csv 'C:\temp\machines.csv' | % {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",
$_.machinename)
$key = $reg.OpenSubkey($keyname)
$value = $key.GetValue('AutoAdminLogon')
if ($value -eq "1") {
$filename = Join-Path "c:\temp\autologin" $_.machinename
try {
touch $filename
$textFile = Get-Item $filename
} catch {
$_
}
}
Write-Host ($_.machinename , $value)
}
I have an apps directory in my dropbox - I'd like to be able to access all of them from the command line without having to set up loads and loads of path variables. Is there any way to set up a recursive path variable? I tried putting ** at the end - no joy.
You can't use placeholders or anything like that in the PATH environment variable. It's just a concatenation of directories, no additional features.
So either add all of the app directories to the PATHenvironment variable or think about other ways to solve the problem. For example, you could add one directory to the PATH and place batch files named like the apps there that start the apps.
Made an account for this 11 year old question.
$path = Read-Host -Prompt "Enter the exact path that needs to be recursively added to the PATH env:"
$items = gci -Path $path -Recurse -Directory -name
$nuPath = $env:Path
$r = 0
write-Host "Env started as $nuPath"
foreach ($iitem in $items){
$addpath = ($path + "\" + $iitem)
$executabledir = $addpath + '\' + "*.exe"
if(test-path $executabledir){
Write-Host $addpath
$regexAddPath = [regex]::Escape($addPath)
$arrPath = $nuPath -split ';' | Where-Object {$_ -notMatch "^$regexAddPath\\?"}
$nuPath = ($arrPath + $addPath) -join ';'
++$r
}
}
$result = ($path + ";" + $nupath) -join ';'
$temp = New-TemporaryFile
$result.ToString() > $temp
Start-Process notepad.exe -ArgumentList $temp.FullName
$title = 'WARNING'
$question = "Your new environmental variable for PATH will be in the notepad window that popped up. are you sure you want to continue?"
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0 -and $r -gt 5) {
$title = 'Are you really sure?'
$question = 'This is larger than 5 entries and this can ruin your day if you mess it up. Just doublechecking everything is OK'
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
$env:Path > $HOME\pathbkup.txt
[Environment]::SetEnvironmentVariable("Path", $result, "Machine")
}
else {
Write-Host 'cancelled'
}
}
else {
Write-Host 'cancelled'
}
Remove-Item $temp