PowerShell -WebClient DownloadFile Wildcards? - c#

I want to copy multiple files from a SharePoint libary to a local directory.
It is possible to use Wildcards?
The following code is not working. But is there a way to use the WebClient and Wildcards?
(I must use the WebClient. It is not possible to use the SharePoint WebServices :-( )
$url = "http://mySharePoint/websites/Site/TestDocBib/*.jpg"
$path = "D:\temp\"
$WebClient = New-Object System.Net.WebClient
$WebClient.UseDefaultCredentials = $true
$WebClient.DownloadFile($url, $path)

No, sorry, you can't use wildcards with WebClient. It's not part of HTTP.

What about using WEBDAV?
c:\> copy \\my.sharepoint.site\sites\foo\doclib\*.jpg c:\temp\
If the client end (i.e. not sharepoint) is a server 2008+ platform, you'll need to add the "desktop experience" role and enable the "webclient" service. This is not the same thing as system.net.webclient; it's the HTTP/DAV network redirector service.
If you need to log in with different credentials, you can use this:
c:\> net use * "http://my.sharepoint.site/sites/foo/doclib" /user:foobar
mapped h: to ...
c:\> copy h:\*.jpg c:\temp
Hope this helps.

you can parse though the html of the list.
# dummy url - i've added allitems.aspx
$url = "http://mySharePoint/websites/Site/TestDocBib/allitems.aspx"
$path = "D:\temp\"
$dl_file = $path + "allitems.html"
$WebClient = New-Object System.Net.WebClient
$WebClient.UseDefaultCredentials = $true
$WebClient.DownloadFile($url, $dl_file)
once you've downloaded the file you can parse though the file - a quick google turned up that Lee Holmes had done most of it already:
http://www.leeholmes.com/blog/2005/09/05/unit-testing-in-powershell-%E2%80%93-a-link-parser/
the main bit you want is the regex:
$regex = “<\s*a\s*[^>]*?href\s*=\s*[`"']*([^`"'>]+)[^>]*?>”
I very quick hack - that may (or may not) work... but the gist is there :)
$test = gc $dl_file
$t = [Regex]::Matches($test, $regex, "IgnoreCase")
$i = 0
foreach ($tt in $t) {
# this assumes absolute paths - you may need to add the hostname if the paths are relative
$url = $tt.Groups[1].Value.Trim()
$WebClient = New-Object System.Net.WebClient
$WebClient.UseDefaultCredentials = $true
$WebClient.DownloadFile($url, $($path + $i + ".jpg"))
$i = $i + 1
}

Related

EnumerateFiles Method, UnauthorizedAccessException, and compiling C# in PowerShell [duplicate]

This works to count *.jpg files.
PS C:\> #([System.IO.Directory]::EnumerateFiles('C:\Users\Public\Pictures', '*.jpg', 'AllDirectories')).Count
8
How can an -ErrorAction Continue be applied to this?
PS C:\> #([System.IO.Directory]::EnumerateFiles('C:\Users', '*.jpg', 'AllDirectories')).Count
An error occurred while enumerating through a collection: Access to the path 'C:\Users\Administrator' is denied..
At line:1 char:1
+ #([System.IO.Directory]::EnumerateFiles('C:\Users', '*.jpg', 'AllDire ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I don't think you can. Unless you want to implement directory traversal yourself you're probably stuck with something like this:
Get-ChildItem 'C:\Users' -Filter '*.jpg' -Recurse -Force -ErrorAction SilentlyContinue
Ansgar Wiechers' helpful answer shows a workaround using Get-ChildItem, which is necessary when using the full, Windows-only .NET Framework (FullCLR), on which Windows PowerShell is built.
By contrast, .NET Core v2.1+ - on which PowerShell Core is built - does offer a solution:
#([System.IO.Directory]::EnumerateFiles(
'C:\Users',
'*.jpg',
[System.IO.EnumerationOptions] #{
IgnoreInaccessible = $true
RecurseSubDirectories = $true
}
)).Count
Note that this is the equivalent of -ErrorAction Ignore, not Continue (or SilentlyContinue), in that inaccessible directories are quietly ignored, with no way to examine which of them were inaccessible afterwards.
The solution above is based on this System.IO.Directory.EnumerateFiles() overload, which offers a System.IO.EnumerationOptions parameter.
The above answers work around the issue. They donnot appy the error action.
To realy catch the error action in the .net call, I'm using the $ErrorActionPreference variable in Windows PowerShell, as descirbed in https://devblogs.microsoft.com/scripting/handling-errors-the-powershell-way/:
# Store $ErrorActionPreference
$OldErrorActionPreference = $ErrorActionPreference
# Set $ErrorActionPreference for .net action
# see https://devblogs.microsoft.com/scripting/handling-errors-the-powershell-way/ for other values
$ErrorActionPreference = 'SilentlyContinue'
# .net call
#([System.IO.Directory]::EnumerateFiles('C:\Users\Public\Pictures', '*.jpg', 'AllDirectories')).Count
# restore origional $ErrorActionPreference
$ErrorActionPreference = $OldErrorActionPreference

PowerShell Set Drive Labels Persisting And Unchangeable Until Reboot

Our software needs to map a network drive depending on which database the User logs in to.
The software first checks that the drive isn't already mapped, if it is then it skips the mapping step.
If the drive isn't mapped, or it is mapped to a different share (i.e. the User was previously logged in to a different database), then it clears any existing drive mapping, and maps the required drive.
It does this by generating and then running a PowerShell script.
Remove-SmbMapping -LocalPath "R:" -Force -UpdateProfile;
Remove-PSDrive -Name "R" -Force;
net use "R" /delete /y;
$password = ConvertTo-SecureString -String "Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "Username", $password;
New-PSDrive -Name "R" -PSProvider "FileSystem" -Root "\\server\share" -Credential $credential -Persist;
$a = New-Object -ComObject shell.application;
$a.NameSpace( "R:" ).self.name = "FriendlyName";
The first three lines remove any existing mapping on that drive letter. They all theoretically do the same thing, however thanks to Microsoft it's entirely random which line will actually work. It only consistently works if all three lines are run.
The middle three lines map the new drive.
The last two lines change the drive label of the new drive to something more user-friendly than the default \\server\share label
The first time someone logs in after a reboot the above script works perfectly. The new drive is mapped, and the label is changed.
However, if the User then logs out and logs into a different database, the label will not change.
For example, the User first logs in to 'Database A', and the drive is mapped with the label 'DatabaseAFiles'. All well and good.
But if the User then logs out, and logs in to 'Database B', the drive is correctly mapped and points to the correct share, but the label still says 'DatabaseAFiles' and not 'DatabaseBFiles'.
If the User reboots their PC, however, and logs in to 'Database B', then the label will correctly say 'DatabaseBFiles', but any subsequent log ins to other databases again won't change the label.
Reboot
Log in to Database A, label is DatabaseAFiles
Log out and into Database B, label is still DatabaseAFiles
Reboot
Log in to Database B, label is now DatabaseBFiles
This is not dependent on the last two script lines being present (the two that set the label), I actually added those to try to fix this issue. If those two lines are removed, the label is the default \\server\share label, and still doesn't change correctly, i.e.
Reboot
Log in to Database A, label is \\servera\sharea
Log out and into Database B, label is still \\servera\sharea
Reboot
Log in to Database B, label is now \\serverb\shareb
Regardless of the label, the drive is always correctly mapped to the correct share, and using it has all the correct directories and files.
Everything works correctly, it's just the label that is incorrect after the first login per reboot.
The script is run from within a C# program in a created PowerShell instance
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(script);
IAsyncResult result = PowerShellInstance.BeginInvoke();
while (result.IsCompleted == false)
{
Thread.Sleep(1000);
}
}
As it maps a drive, it cannot be run in Adminstrator mode (the drive won't be mapped for the actual User), it has to be run in normal mode, so there is a check earlier up for that.
If I take a copy of the script and run it in a PowerShell session outside the C# program, I get exactly the same results (everything works but the label is wrong after the first login), so it's not that it's being run from within the C# program.
It's entirely possible that the issue is with either File Explorer or with Windows, either caching the label somewhere and reusing it could be the problem, of course.
Anyone have any suggestions of things I can try please?
A time ago, I have had to rename file shares and therefor I wrote this function. Maybe this is helpful for you.
#--------------------------------------
function Rename-NetworkShare {
#--------------------------------------
param(
[string]$sharePattern,
[string]$value
)
$regPath = Get-ChildItem 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2'
$propertyName = '_LabelFromReg'
foreach( $child in $regPath ) {
if( $child.PSChildName -like ('*' + $sharePattern + '*') ) {
if( !$child.Property.Contains( $propertyName ) ) {
New-ItemProperty $child.PSPath -Name $propertyName -PropertyType String | Out-Null
}
Set-ItemProperty -Path $child.PSPath -Name $propertyName -Value $value | Out-Null
}
}
}
Rename-NetworkShare -sharePattern 'patternOldName' -value 'NewFriendlyName'
It's not ideal, there's one bit I'm not happy about, but this is the best I've been able to come up with so far. If I come up with something better I'll post that instead.
Firstly, I check if there is already a drive mapped to the letter I want to use:-
// Test if mapping already exists for this database
var wrongMapping = false;
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
var driveLetter = drive.RootDirectory.ToString().Substring(0, 1);
if (driveLetter == mappingDetails.DriveLetter && Directory.Exists(drive.Name))
{
wrongMapping = true; // Assume this is the wrong drive, if not we'll return from the method before it's used anyway
var unc = "Unknown";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + driveLetter))
{
if (key != null)
{
unc = key.GetValue("RemotePath").ToString();
}
}
if (unc == mappingDetails.Root)
{
View.Status = #"Drive already mapped to " + mappingDetails.DriveLetter + ":";
ASyncDelay(2000, () => View.Close());
return; // Already mapped, carry on with login
}
}
}
If we already have the correct path mapped to the correct drive letter, then we return and skip the rest of the mapping code.
If not, we'll have the variable wrongMapping, which will be true if we have a different path mapped to the drive letter we want. This means that we'll need to unmap that drive first.
This is done via a Powershell script run the by C# program, and contains the bit I'm not happy about:-
Remove-PSDrive mappingDetails.DriveLetter;
Remove-SmbMapping -LocalPath "mappingDetails.DriveLetter:" -Force -UpdateProfile;
Remove-PSDrive -Name "mappingDetails.DriveLetter" -Force;
net use mappingDetails.DriveLetter /delete /y;
Stop-Process -ProcessName explorer;
The first four lines are different ways to unmap a drive, and at least one of them will work. Which one does work seems to be random, but between all four the drives (so far) always get unmapped.
Then we get this bit:
Stop-Process -ProcessName explorer;
This will close and restart the Explorer process, thus forcing Windows to admit that the drive we just unmapped is really gone. Without this, Windows won't fully release the drive, and most annoyingly it will remember the drive label and apply it to the next drive mapped (thus making a mapping to CompanyBShare still say CompanyAShare).
However, in so doing it will close any open File Explorer windows, and also briefly blank the taskbar, which is not good.
But, given that currently no Company sites have more than one share, and it's only the Developers and Support that need to remove existing drives and map new ones, for now we'll put up with it.
Once any old drive is unmapped, we then carry on and map the new drive, which again is done via a PowerShell script run from the C# code.
$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;
$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';
New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";
The first part maps the drive:
$password = ConvertTo-SecureString -String "mappingDetails.Password" -AsPlainText -Force;
$credential = New-Object System.Management.Automation.PSCredential -ArgumentList "mappingDetails.Username", $password;
New-PSDrive -Name "mappingDetails.DriveLetter" -PSProvider "FileSystem" -Root "mappingDetails.Root" -Credential $credential -Persist;
The middle part changes the name directly:
$sh=New_Object -com Shell.Application;
$sh.NameSpace('mappingDetails.DriveLetter:').Self.Name = 'friendlyName';
And the end part changes the name in the Registry:
New-Item –Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\" –Name "foldername";
Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg";
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\foldername" -Name "_LabelFromReg" -Value "friendlyName" -PropertyType "String\";
Firstly, it creates a key for this path (it the key already exists it'll fail but the script will carry on)
Then it removes the existing property _LabelFromReg (if it doesn't exist it'll fail but the script will carry on)
Then it (re)creates the property _LabelFromReg with the new friendlyname.
So, again doing the same thing two ways, but between the two it works.
I'd like to find some alternative to having to kill and restart the Explorer process, it's really tacky, but it seems to be the only way to get Windows to acknowledge the changes.
And at least I now get the correct labels on the drives when mapped.

Password to NTLM from dump file

I am in security and want to consume a massive password dump file (3GB) as part of my usual password audits
The file is delimited into two columns, SHA1, and the actual password
For my purposes, because Windows stores password as NTLM hashes at rest (Kerberos only used during transport) I need passwords in NTLM, not SHA1. (You can easily prove it to yourself by doing a password dump, I use DSInternals)
I am currently converting clear-text passwords to NTLM with this script
#Install-Module DSInternals
Import-Module DSInternals
$reader = [System.IO.File]::OpenText("C:\...\68_linkedin_found_hash_plain.txt")
try {
for() {
$line = $reader.ReadLine()
if ($line -eq $null) { break }
$pwd = ConvertTo-SecureString $line.Split(':')[1] -AsPlainText -Force
$hash = ConvertTo-NTHash $pwd;
Add-Content C:\...\68_linkedin_ntlm.txt $hash
}
}
finally {
$reader.Close()
}
Any obvious way of processing this faster? I suppose I can ingest into a DB and process it threaded via a little C# app but maybe there's something quick and dirty?
The file format is (no these are not my passwords, these are passwords from a common password dump file that is publicly available)
8c9fcfbf9ead0d63d04b5d3120c42cb885af899e:16piret
8c9fd045ee531744a4fdc1f52e59c3878e742ee0:louie310
8c9fd070274a0eebecf58f8f50e283bf53cec215:kery62
8c9fd08d1c17266f7c1e42a3f16a1161613c7572:sa81nt
8c9fd1093bd8592bbaea195785f8d1c81589073f:cuchilleros
8c9fd1a963bbf44ea9b531e91e5cb1b591c454cc:198962914685590
8c9fd1d8cc6d4fa8164a2fcb3adc7a45f3409547:sculp1011
8c9fd20540d66831f6f65a39ce1bca0e654fd5e6:ume1431965
8c9fd2b4a9571db21c4226bf9ecaea282ecadd5e:534015629819772
8c9fd2f3e63c20314cc962b624178ba82c6674a7:siegenthaler
8c9fd3713fe9600d2bea05b4e8cd33efe12bddb1:mkenrick
8c9fd3a39cca8fb8cdeeb52999aed7e6e9435fd3:billscot
8c9fd3b96ee1485e0fd7d6c71ffe3efd2e8a4614:ndiyehova
8c9fd43aef9804dab6e0aebc58415543175fea00:662566123
8c9fd481cf8f35edb6ebd683fffb0efce0478f21:371874conv
8c9fd4f37632294093fb057eb0168a05d9396e74:h3aww7w
8c9fd53dce9b046f73c5f298e2f694213f8f90f1:squishy23
8c9fd55206e0525d119f4946d3ae75e347cccb4b:NEH3112
8c9fd555303ac08f9103ff8451f8c05cf48cf120:marco22580
8c9fd5c6a94b1171518d0ba264033d779a075e8c:Nowornever2010
8c9fd613fb632b5bc6ae20a671aa40decdb8609a:MKSmks1976##
8c9fd627a48f9971df5bee874501156e9d3c011d:Steripro5
TIA
EDIT:
By reading into memory and writing to separate files speed up the process a bit. Also used suggestion from TessellatingHeckler
Import-Module DSInternals
$lines = [System.IO.File]::ReadAllLines('C:\...\68_linkedin_found_hash_plain.txt')
foreach($line in $lines) {
try {
$password = $line.Substring($line.IndexOf(':')+1);
if ($password.Length -lt 128)
{
$pwd = ConvertTo-SecureString $line.Substring($line.IndexOf(':')+1) -AsPlainText -Force
$hash = ConvertTo-NTHash $pwd;
Set-Content C:\Temp\Hashes\$hash.txt $hash
}
}
finally {
}
}
The afterwards I can combine the files with
copy *.txt combined.log
If those are typical line lengths, and your file is 3GB, we're talking 50-60 million lines.
Change $line.Split(':')[1] to $line.Substring($line.IndexOf(':')+1), that will save creating and cleaning up 50 million arrays and 50 million strings of the bit you don't want. (Is that right? Your example file format has the hash on the left, your use of [1] will pick the username part?)
PowerShell calling the .Net static methods like [system.io.file] is reasonably fast, but these bits:
$pwd = ConvertTo-SecureString $line.Split(':')[1] -AsPlainText -Force
$hash = ConvertTo-NTHash $pwd;
Add-Content C:\...\68_linkedin_ntlm.txt $hash
have a huge overhead. Starting and initializing cmdlets costs a lot more than function calls in other languages, and having add-content close/open the file 50 million times adds needless file system overhead. Change that so you open the file once, and write to it in the loop:
# before the loop
$outStream = [System.IO.StreamWriter]::new(
[System.IO.FileStream]::new(
'c:\path\output.txt',
[system.io.filemode]::OpenOrCreate))
# in the loop
$outStream.WriteLine($hash)
# after the loop
$outStream.Close()
The next bit would be to see if you can get the code which does ConvertTo-SecureString and ConvertTo-NTHash and inline it. I don't know what the NTHash one is, but ConvertTo-SecureString source is here, it's not going to be trivial to wrap / inline that into PowerShell code.
That's it as far as I can see for "quick and dirty", but it might knock some 20-30% off the runtime.

How To Pin To Start Special Folders With No Target

How do you pin to start special folders using powershell? Like "ThisPC", iexplorer
This will pin to start exe's fine, but what about windows explorer and myComputer? How to pin those items since they have no target?
Given this
<start:DesktopApplicationTile Size="2x2" Column="0" Row="0" DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Windows System\This PC.lnk" />
It seems to have issues with .lnk's for "This PC", "File Explorer", etc
Function PinLnk
{
Param
(
[Parameter(Mandatory,Position=0)]
[Alias('p')]
[String[]]$Path
)
$Shell = New-Object -ComObject Shell.Application
$Desktop = $Shell.NameSpace(0X0)
$WshShell = New-Object -comObject WScript.Shell
$Flag=0
Foreach($itemPath in $Path)
{
$itemName = Split-Path -Path $itemPath -Leaf
#pin application to windows Start menu
$ItemLnk = $Desktop.ParseName($itemPath)
$ItemVerbs = $ItemLnk.Verbs()
Foreach($ItemVerb in $ItemVerbs)
{
If($ItemVerb.Name.Replace("&","") -match "Pin to Start")
{
$ItemVerb.DoIt()
$Flag=1
}
}
}
}
PinLnk "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe"
I tried this approach as well still not pinning mycomputer to start
PS C:\WINDOWS\system32> Function PinLnk14
>> {
>>
>> $shell = new-object -com Shell.Application
>> $folder = $shell.NameSpace("shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}") # ssfDRIVES
>> $ItemVerbs=$folder.Self.Verbs()
>> Foreach($ItemVerb in $ItemVerbs)
>> {
>> Write-Output $ItemVerb.Name
>> If($ItemVerb.Name.Replace("&","") -match "Pin to Start")
>> {
>> Write-Output "TRYING TO PIN"
>> $ItemVerb.DoIt()
>> }
>> }
>> }
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> Pinlnk14
&Open
Pin to Quick access
Mana&ge
&Pin to Start
TRYING TO PIN
Map &network drive...
Dis&connect network drive...
Create &shortcut
&Delete
Rena&me
P&roperties
Most special folders are accessible using special values, documented here: ShellSpecialFolderConstants enumeration
So, if you want to get the My Computer (a.k.a. "This PC") folder, you can do this:
$shell = new-object -com Shell.Application
$folder = $shell.NameSpace(17) # "ssfDRIVES" constant
And this will get you a Folder object.
There is another way wich uses the folder CLSID (a guid). It will allow you to get to any folder in what's called the shell namespace, even the ones that may not be defined in the enumeration above (3rd party shell namespace extensions for example). The syntax is this:
$shell = new-object -com Shell.Application
$folder = $shell.Namespace("shell:::{CLSID}")
In fact, this funny syntax combines the 'shell:' URI moniker with the shell namespace parsing syntax ::{CLSID}.
So for example to get the My Computer folder, you would do use the constant known as CLSID_MyComputer like this:
$shell = new-object -com Shell.Application
$folder = $shell.Namespace("shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}")
This will also work:
$shell = new-object -com Shell.Application
$folder = $shell.Namespace("shell:MyComputerFolder") # direct shell: syntax
And it should bring you back the same object as in the previous call. They're all equivalent.
Once you have a Folder object, there is a last trick to get the associated verbs, because the Verbs() method is only defined on the FolderItem object.
To get the FolderItem from the Folder (as a Folder is also an "item" in the namespace, so it has also a FolderItem facade), you can use the Self property, like this:
$shell = new-object -com Shell.Application
$folder = $shell.NameSpace("shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}") # ssfDRIVES
$folder.Self.Verbs()
Ok, that was to get verbs for a FolderItem. To pin an item to start however, the "Pin to Start" verb invocation does not always work (for My Computer for example), or the verb isn't even available (for a standard folder for example). In general, it doesn't really work well for folders for some reason.
So, one solution for folders is to first create a shortcut file (.lnk) somewhere to that folder (including My Computer or other special locations), and pin that shortcut file to start. Note: the standard (non language localized) verb for Pin to Start is "PinToStartScreen", it's better to use that than to scan various verbs (all verbs have a canonical name). So the code would look like this to pin My Computer to start:
$wshell = new-object -com WScript.Shell
$shortcut = $wshell.CreateShortcut("c:\temp\mypc.lnk")
$shortcut.TargetPath = "shell:MyComputerFolder" # use the same syntax as described above
$shortcut.Save()
$shell = new-object -com Shell.Application
$folder = $shell.NameSpace("c:\temp")
$lnk = $folder.ParseName("mypc.lnk") # get the shortcut file
$lnk.InvokeVerb("PinToStartScreen") # invoke "Pin To Start" on the shortcut file
The fact is that's exactly what Windows does when we do "Pin to Start" on My Computer, it creates a shortcut in C:\Users\<my user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs

WPF Progress Bar PowerShell

I have been trying to get a WPF progress bar in to my PowerShell script for awhile now, but i can not figure it out. I have tried probably 13 different ways but to no avail. Here is my code:
$WPFRunQuick_Button.Add_Click({
<# Add-Type -AssemblyName System.Windows.Forms
$Form.Text = "Processing"
$Label = New-Object System.Windows.Forms.Label
$Font = New-Object System.Drawing.Font("Times New Roman",16,[System.Drawing.FontStyle]::Bold)
$form.Font = $Font
$Form.Controls.Add($Label)
$Label.Text = "You have run the quick test, please be patient as it runs."
$Label.AutoSize = $True
$form.autosize = $true
$form.AutoScroll = $true
$form.autosizemode = "GrowAndShrink"
$Form.MinimizeBox = $False
$Form.MaximizeBox = $False
$Form.SizeGripStyle = "Hide"
$form.startposition = "CenterScreen"
$Form.Visible = $True
$Form.Update() #>
$max = 100
For($i = 1; $i -le $max; $i++){
Write-progress -Activity “Device Check” -Status “Testing, please wait.” `
-percentcomplete ($i / $max*100) -id 1
sleep 1
}
# Quick Test Run Function
Quick_Test_Run
# Prompt to send logs to associate support
$a = new-object -comobject wscript.shell
$intAnswer = $a.popup("Do you want to send these results?", 0,"Results",4)
if ($intAnswer -eq 6) {
$a.popup("Results Sent")
# Create email
$EmailSubject = "Tasa Test"
$EmailTo = "usupportlogs#test.com"
$EmailBody = [IO.File]::ReadAllText("C:\Temp\PMCS_TicketLogs.log")
# Send email to usupportlogs inbox
Send-MailMessage -From "PMCS_No_Reply#test.com" -To $EmailTo -subject $EmailSubject -Body $EmailBody -SmtpServer "SMTPRR.Cerner.Com"
} else {
$a.popup("Sending Cancelled")
}
$Form.Close()
})
When i try to put one in before i call the quick test function, it will load all the way then launch the test, and if i put it in after it will do the test and then launch the progress bar. I just want a simple progress bar for this script. Any help or insight would be appreciated, thanks!
In a script I created recently, my GUI application would check availability of a series of computers in our corporate estate by iterating through a user-submitted list of IP Addresses (a System.Windows.Forms.TextBox named in my script as $boxIpInput) and perform a Test-Connection conditional statement on each one. To allow the users to see what progress the scripts had made on the ping tests, I used a System.Windows.Forms.ProgressBar to communicate the progress of the script; I found this out by googling for "Powershell Forms Progress Bar" and happened upon this link.
To create the ProgressBar object, I used the following code;
$barPingProgress= New-Object System.Windows.Forms.ProgressBar
$barPingProgress.Size= New-Object System.Drawing.Size(274,20);
$barPingProgress.Location= New-Object System.Drawing.Size(8,440);
$barPingProgress.Style= 'Continuous'
$barPingProgress.Value= 0
$objForm.Controls.Add($barKioskPingProgress);
Upon clicking the $btnRunIpCheck Button to perform the ping test, I used the input from $boxIpInput and piped it into a ForEach-Object loop to incrementally increase the count of the progress bar in a similar way to your For loop before the Quick_Test_Run function (I have removed all irrelevant code from the below snippet for brevity);
//other button initialisation code (removed)
...
$btnRunIpCheck.Add_Click({
//some code to validate entries (removed)
...
$arrTmpIpAddresses= $boxIpInput.Text.Split(" `n",[System.StringSplitOptions]::RemoveEmptyEntries)
//some code to check size of list and warn users of the time it would take (removed)
...
$arrTmpIpAddresses | ForEach-Object {
$intOrderCount++
[int]$tmpProgNum= ($intOrderCount/$arrTmpIpAddresses.Count) * 100
$barPingProgress.Value= $tmpProgNum
//ping test code (removed)
}
}
}
Personally, I would recommend using the System.Windows.Forms.ProgressBar object over the Write-Progress method you're using at present; in the case of your code, you can either add the ProgressBar object as a part of the Add_Click event or have it created when you generate the main $Form object and then begin the Value incrementation during the Add_Click event.
To further help you, I'd need to understand what it is that you're counting the progress against - I can't really see what you're counting against (aside from the "Device Check" part that is) so I don't feel comfortable incorporating an example of how the ProgressBar Form would work in the context of your code. If you want me to help you further, you'll need to give me a little more detail. :)

Categories