Function not found on first attempt - function

I have a script, which creates a GUI to put in parameters to create an AD user. The script contains a large function (one that creates a AD browser that I found on the net). The function is called right at the start of the script (one textbox requires input from the function to prefill a value) or by clicking a button in the GUI. Problem is, that when I run the script, I get error message saying, that "The term 'Browse-AD' (=name of the function) is not recognised as the name of a cmdlet, function..." When I run it from PowerShell ISE, I get this error for the first time I try, the script launches (but the browser doesn't work) and after cancelling the script and running it again the browser works fine, no error message is displayed. I didn't touch the script in the mean time, I just run it for the second time, no changes made. But the bigger problem is, that when the script is launched from a file directly (which is naturally its intended way of usage), it's always the first attempt and therefore the error is always displayed and browser doesn't work. Any idea what's happening?
This is the function:
function Browse-AD()
{
# original inspiration: https://itmicah.wordpress.com/2013/10/29/active-directory-ou-picker-in-powershell/
# author: Rene Horn the.rhorn#gmail.com
<#
Copyright (c) 2015, Rene Horn
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
$dc_hash = #{}
$selected_ou = $null
Import-Module ActiveDirectory
$forest = Get-ADForest
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
function Get-NodeInfo($sender, $dn_textbox)
{
$selected_node = $sender.Node
$dn_textbox.Text = $selected_node.Name
}
function Add-ChildNodes($sender)
{
$expanded_node = $sender.Node
if ($expanded_node.Name -eq "root") {
return
}
$expanded_node.Nodes.Clear() | Out-Null
$dc_hostname = $dc_hash[$($expanded_node.Name -replace '(OU=[^,]+,)*((DC=\w+,?)+)','$2')]
$child_OUs = Get-ADObject -Server $dc_hostname -Filter 'ObjectClass -eq "organizationalUnit" -or ObjectClass -eq "container"' -SearchScope OneLevel -SearchBase $expanded_node.Name
if($child_OUs -eq $null) {
$sender.Cancel = $true
} else {
foreach($ou in $child_OUs) {
$ou_node = New-Object Windows.Forms.TreeNode
$ou_node.Text = $ou.Name
$ou_node.Name = $ou.DistinguishedName
$ou_node.Nodes.Add('') | Out-Null
$expanded_node.Nodes.Add($ou_node) | Out-Null
}
}
}
function Add-ForestNodes($forest, [ref]$dc_hash)
{
$ad_root_node = New-Object Windows.Forms.TreeNode
$ad_root_node.Text = $forest.RootDomain
$ad_root_node.Name = "root"
$ad_root_node.Expand()
$i = 1
foreach ($ad_domain in $forest.Domains) {
Write-Progress -Activity "Querying AD forest for domains and hostnames..." -Status $ad_domain -PercentComplete ($i++ / $forest.Domains.Count * 100)
$dc = Get-ADDomainController -Server $ad_domain
$dn = $dc.DefaultPartition
$dc_hash.Value.Add($dn, $dc.Hostname)
$dc_node = New-Object Windows.Forms.TreeNode
$dc_node.Name = $dn
$dc_node.Text = $dc.Domain
$dc_node.Nodes.Add("") | Out-Null
$ad_root_node.Nodes.Add($dc_node) | Out-Null
}
return $ad_root_node
}
$main_dlg_box = New-Object System.Windows.Forms.Form
$main_dlg_box.ClientSize = New-Object System.Drawing.Size(400,600)
$main_dlg_box.MaximizeBox = $false
$main_dlg_box.MinimizeBox = $false
$main_dlg_box.FormBorderStyle = 'FixedSingle'
# widget size and location variables
$ctrl_width_col = $main_dlg_box.ClientSize.Width/20
$ctrl_height_row = $main_dlg_box.ClientSize.Height/15
$max_ctrl_width = $main_dlg_box.ClientSize.Width - $ctrl_width_col*2
$max_ctrl_height = $main_dlg_box.ClientSize.Height - $ctrl_height_row
$right_edge_x = $max_ctrl_width
$left_edge_x = $ctrl_width_col
$bottom_edge_y = $max_ctrl_height
$top_edge_y = $ctrl_height_row
# setup text box showing the distinguished name of the currently selected node
$dn_text_box = New-Object System.Windows.Forms.TextBox
# can not set the height for a single line text box, that's controlled by the font being used
$dn_text_box.Width = (14 * $ctrl_width_col)
$dn_text_box.Location = New-Object System.Drawing.Point($left_edge_x, ($bottom_edge_y - $dn_text_box.Height))
$main_dlg_box.Controls.Add($dn_text_box)
# /text box for dN
# setup Ok button
$ok_button = New-Object System.Windows.Forms.Button
$ok_button.Size = New-Object System.Drawing.Size(($ctrl_width_col * 2), $dn_text_box.Height)
$ok_button.Location = New-Object System.Drawing.Point(($right_edge_x - $ok_button.Width), ($bottom_edge_y - $ok_button.Height))
$ok_button.Text = "Ok"
$ok_button.DialogResult = 'OK'
$main_dlg_box.Controls.Add($ok_button)
# /Ok button
# setup tree selector showing the domains
$ad_tree_view = New-Object System.Windows.Forms.TreeView
$ad_tree_view.Size = New-Object System.Drawing.Size($max_ctrl_width, ($max_ctrl_height - $dn_text_box.Height - $ctrl_height_row*1.5))
$ad_tree_view.Location = New-Object System.Drawing.Point($left_edge_x, $top_edge_y)
$ad_tree_view.Nodes.Add($(Add-ForestNodes $forest ([ref]$dc_hash))) | Out-Null
$ad_tree_view.Add_BeforeExpand({Add-ChildNodes $_})
$ad_tree_view.Add_AfterSelect({Get-NodeInfo $_ $dn_text_box})
$main_dlg_box.Controls.Add($ad_tree_view)
# /tree selector
if ($main_dlg_box.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK)
{
return $dn_text_box.Text
}
return $null
}
This is definition of the button that shall launch the browser:
$Select_AD_path = New-Object System.Windows.Forms.Button
$Select_AD_path.Text = "Browse..."
$Select_AD_path.Width = 100
$Select_AD_path.Height = 30
$Select_AD_path.Location = New-Object System.Drawing.Point(90,155)
$Select_AD_path.Font = 'Microsoft Sans Serif,10'
$Select_AD_path.add_Click({
$dn = Browse-AD
if ($dn)
{
$Location_val.Text = $dn
}
})
Also, I put the function to the value of a textbox, so that the browser launches before the script itself runs and prefills the AD path directly to the value I need it:
$Location_val = New-Object system.Windows.Forms.TextBox
$Location_val.multiline = $false
$Location_val.text = Browse-AD
$Location_val.width = 250
$Location_val.height = 20
$Location_val.location = New-Object System.Drawing.Point(200,160)
The precise error message is this:
Browse-AD : The term 'Browse-AD' is not recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Users\a.Petr.Synek\Documents\New_ADuser_woodgroup_complete.ps1:204 char:42
+ $Location_standard_val.Text = Browse-AD
+ ~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Browse-AD:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
At the 204th row there is this text: $Location_val.text = Browse-AD

As requested, my comment as answer:
In your script, you need to put the Browse-AD function on top and all the rest of your code beneath that.
If this order is incorrect, the first time running, the function is called before it has been parsed, giving you the error message.
The second time, PowerShell already parsed the entire code, so then the function is known. PowerShell (unlike VBScript) parses code from top to bottom.

Related

Downscale database dtu during out of office hours, it should happen every day automatically, is it possible?

I have multiple databases running in each environment which are charging me a lot of cost each month, so i wanted to downscale the DTUs to some lower count during non-working hours, again during working hours DTUs to be upscale back to actual DTUs count, it should happen automatically as per time settings every single day. If it's possible, can anyone please help me out with this?
I am trying write a terraform script and run the azure devops pipeline, deploy it in each subscription/environment as it automatically applies in each environment databases.
You can do this by creating Azure Automation Account and use runbooks to do the scheduled job. Using PowerShell/PowerShell Workflow as a runbook type would be better for this case. You can create 1 runbook for scale down and 1 for scale up.
You can use resource tags as your reference point for the target azure sql database/s. See link on how to add tags a resource in Azure.
You can now use this tag as your parameter in your runbook script to view and make some changes on the target azure sql database.
Here is sample code snippet:
workflow ScaleDown_DBs_S0_Tags
{
Param(
[Parameter(Mandatory=$true)]
[String]
$ScaleDown_Schedule_TagValue
)
InlineScript{
$NewEdition = "Standard"
$NewTier = "S0"
#Converting Tag parameter for use in InlineScript
$TagValue = $Using:ScaleDown_Schedule_TagValue
#Getting the current subscription details
$Context = Get-AzureRmContext
#Getting the current subscription name
$CurrentSub = $Context.Subscription.Name
$Time = (get-date).ToString('T')
Write-Output "Starting job at $($Time)"
Write-Output ""
Write-Output "Getting all Databases with tags $($TagValue) under $($CurrentSub) Subscription..."
#Getting all database objects with tags as per tag parameter
$TaggedDBObjects = Get-AzureRmResource | where {$_.Tags.Values -like "*$TagValue*"}
$count = $TaggedDBObjects.count
Write-Output "Total of $($Count) databases found:"
$TaggedDBObjects.Name
foreach ($TaggedDBObject in $TaggedDBObjects) {
$SplitName = $TaggedDBObject.Name.Split('/')
$SqlServerName = $SplitName[0]
$DatabaseName = $SplitName[1]
$ResourceGroupName = $TaggedDBObject.ResourceGroupName
Write-Output ""
Write-Output "Checking current Edition/Tier of $($DatabaseName)..."
#Getting database complete object using Get-AzureRmSqlDatabase
$GetDBs = Get-AzureRmSqlDatabase -ServerName $SqlServerName -ResourceGroupName $ResourceGroupName -DatabaseName $DatabaseName
$CurrentEdition = $GetDBs.Edition
$CurrentTier = $GetDBs.CurrentServiceObjectiveName
#Validating if Edition is already set and if action needed, if false
if($CurrentTier -eq $NewTier){
Write-Output "Database $($DatabaseName) is already under $($CurrentEdition)/$($CurrentTier) plan. No action will be taken"
}
Else{
Write-Output ""
Write-Output "Current plan is $($CurrentEdition)/$($CurrentTier)"
Write-Output ""
Write-Output "Begin vertical scaling of database $($DatabaseName) to $($NewEdition)/$($NewTier) plan"
Write-Output ""
#Changing Edition/Tier accordingly
Set-AzureRmSqlDatabase -DatabaseName $DatabaseName -ServerName $SqlServerName -ResourceGroupName $ResourceGroupName -Edition $NewEdition -RequestedServiceObjectiveName $NewTier -AsJob
Write-Output ""
Write-Output "Database $($DatabaseName) successfully scaled to $($NewEdition)/$($NewTier)"
}
}
}
}
After working with the script, you can now create a schedule and link it to your runbook.

How do I get EWS conflictresolutionmode to work

I will need to go through many mailboxes and remove 'copy:' from the subject line of lots of meetings. At the moment the code works fine except that after the code performs the conflictresoltuionmode, alwaysoverwrite and I can see the successful result it does not save the update.
I have tried using the save function but its not working
#Variables and constants
$startdate = Get-Date
$enddate = $startdate.AddDays(365)
#$string = 'check'
#create a remote session to exchange
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchangeserver.com/PowerShell/ -Authentication Kerberos
Import-PSSession $Session -DisableNameChecking:$disablenamechecking
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
[Void] [Reflection.Assembly]::LoadFile($dllPath)
$MailboxName = "firstname.lastname#company.com"
$ExchVer = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
$exchService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchVer)
$exchservice.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
$exchService.UseDefaultCredentials = $true
$exchService.AutodiscoverUrl($MailboxName)
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchservice,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar)
$CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)
$FindItems = $calendar.FindAppointments($CalendarView)
if($FindItems.Items.Count -gt 0)
{
foreach($Item in $finditems){
if ($item.Subject -like "check*") {
$item.subject.substring(6)
**$item.update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverWrite)
# the overwrite is successful but it's not saving**
#$item.Subject
}
#$ItemColl.Add($Item)
}
}
$item.Subject
Your code doesn't appear to actually make any change eg
$item.subject.substring(6)
Would simply print the first 6 character of the Subject if you wanted to change the subject you would need to have
$item.subject = "blah blah etc"
You don't have to call save as its only valid if the object is new all that is need is Update but it will only do anything if there is a property that has been changed which there isn't in the above example.

Global Variables from WinForm

I am currently trying to use the values of a date/time picker variable from one function and use it multiple times in another function. The date/time picker is on a form, so when a user sets a start date and end date, those values are assigned to a variable to use in the other function, which should call both those variables at least twice. While trying to call that variable from the first function, it contains a null value even though it was assigned to a variable. I am trying to keep the value that was created in one function to use in another function multiple times. Thank you.
Set-strictMode -off
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
[System.Windows.Forms.Application]::EnableVisualStyles()
Add-Type -AssemblyName System.Web
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
$global:startDate=$null
$global:endDate=$null
function MakeForm{
#region begin GUI
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '396,180'
$Form.text = "Rexpii Integrations Version (Beta)"
$Form.BackColor = "#ffffff"
$Form.TopMost = $False
$Form.Icon= [System.Drawing.Icon]::ExtractAssociatedIcon('C:\files\scripts\tpgicon.ico')
$btnSubmit = New-Object system.Windows.Forms.Button
$btnSubmit.text = "Submit"
$btnSubmit.width = 60
$btnSubmit.height = 30
$btnSubmit.location = New-Object System.Drawing.Point(220,116)
$btnSubmit.Font = 'Microsoft Sans Serif,10'
$btnSubmit.Add_Click({
$global:startDate= $Global:txtStartDate.Value.ToString("yyyy-MM-dd")
$global:endDate= $Global:txtEndDate.Value.ToString("yyyy-MM-dd")
GetData
$Form.Close()})
$btnCancel = New-Object system.Windows.Forms.Button
$btnCancel.text = "Cancel"
$btnCancel.width = 60
$btnCancel.height = 30
$btnCancel.location = New-Object System.Drawing.Point(288,116)
$btnCancel.Font = 'Microsoft Sans Serif,10'
$btnCancel.Add_Click({
$Form.Close()
})
$Global:txtStartDate = New-Object system.Windows.Forms.DateTimePicker
$Global:txtStartDate.width = 150
$Global:txtStartDate.location = New-Object System.Drawing.Point(208,40)
$Global:txtStartDate.Format = "Custom"
$Global:txtstartDate.CustomFormat = "yyyy-MM-dd"
$Global:txtEndDate = New-Object system.Windows.Forms.DateTimePicker
$Global:txtEndDate.width = 150
$Global:txtEndDate.location = New-Object System.Drawing.Point(208,80)
$Global:txtEndDate.Format="Custom"
$Global:txtEndDate.CustomFormat = "yyyy-MM-dd"
$Form.controls.AddRange(#($btnSubmit,$btnCancel,$PictureBox1,$PictureBox2,$StartDate,$EndDate,$lblStartDate,$lblEndDate,$txtStartDate, $txtEndDate))
[void]$Form.ShowDialog()
}
MakeForm
function GetData{
#my scripts number one
Write-Output "This is the first $global:startDate to $global:endDate example"
#my scripts number two
Write-Output "This is the second $global:startDate to $global:endDate example"
}
First, you need to define your functions before you can use them. You are trying to call GetData before it's defined. Move the call to MakeForm (which also shows the form) to after the function definition for GetData.
Second, should change GetData to call Write-Host instead of Write-Output so you can see the output from the event handler. And if you call GetData after showing the form, you'll see that the globals are set properly.
So the last part of your script should look like:
function GetData{
#my scripts number one
Write-Host "This is the first $global:startDate to $global:endDate example"
#my scripts number two
Write-Host "This is the second $global:startDate to $global:endDate example"
}
MakeForm
# Call GetData again to verify that the globals are set.
GetData

Powershell: How to throw an error if a CSV entry is blank

I've written an extensive script that runs through an AD termination process, and the script can obtain the necessary information from a CSV. How do I make it so that it errors out if the entry is blank in the CSV? I've tried putting in Try-Catch, If-Else, everything that I know how to do. I've tried changing the error action, and I can get it to throw system generated errors (ex. "Cannot bind parameter "Identity" to the target..."), but I cannot get it to do what I want. Please see the code example below:
(Yes, I know that I'm duplicating values. This of importance later on in the script, and not the part I'm having issues with)
$owner = $user.'Network User ID'}
$loginID = $user.'Network User ID'
$Identity = Get-ADUser -Identity $owner -Properties Displayname |Select-Object -ExpandProperty Displayname
$manager = $user.'Provide Inbox Access To'
$NewOwner = $user.'Provide users email group ownership to'
$NewOwnerID = $User.'Provide users email group ownership To'
What I need it to do is throw an error if ANY entry in the CSV is blank, and terminate. The most promising idea that I tried was:
If ($Owner -eq $Null)
{
Write-Host "Invalid entry, the Network User ID field cannot be blank"
Write-Host "Press Enter to Exit..."
Exit
}
Else
{
#Do everything else
}
But even that still fails.
In summary, what I need to do is throw a custom terminating error if an entry in the CSV is blank.
Any help is greatly appreciated!
EDIT
If this helps, here is more of the real code...
$Confirmation = Read-Host "Please double check the information in the file. Are you sure you want to continue? (Y/N)"
If($Confirmation -eq "Y")
{
Write-Host "You have chosen to proceed. Processing Termination" -BackgroundColor DarkCyan
#Import file
$file = "C:\TerminateUsers.csv"
$data = Import-Csv $file
#Set disabled OU
$disabledOU = "OU=Users,OU=Disabled Accounts, OU=Corporate"
$colOutput = #()
foreach ($user in $data)
{
#Grab variables from CSV
$owner = $user.'Terminated Network User ID'}
$loginID = $user.'Terminated Network User ID'
#Displayname required for Outlook functions
$Identity = Get-ADUser -Identity $owner -Properties Displayname |Select-Object -ExpandProperty Displayname
$manager = $user.'Provide Inbox Access To'
$NewOwner = $user.'Provide users email group ownership to'
$NewOwnerID = $User.'Provide users email group ownership To'
If (Get-ADUser -LDAPFilter "(sAMAccountName=$loginID)")
{
$date = Get-Date -Format d
#Disable account, change description, disable dialin, remove group memberships
Set-ADUser -Identity $loginID -Enabled $false
Set-ADUser -Identity $loginID -Replace #{Description = "Terminated $date"}
Set-ADUser -Identity $loginID -Replace #{msNPAllowDialin = $False}
RemoveMemberships $loginID
This isn't all of it, but this is the part we're working with...
There's a number of issues you're going to run into here.
First, $Owner -eq $Null isn't going to do what you likely want to do. Mainly, the issue is that an empty string is not a null value. They're different. Instead, your test should be:
if ([string]::IsNullOrEmpty($owner)) { ... }
Or:
if ([string]::IsNullOrWhiteSpace($owner)) { ... }
This second one returns true if the string includes only tabs, spaces, or other whitespace characters, or is an empty string, or is null.
Second, to throw an exception, you need to use the throw keyword. See Get-Help about_Throw. For example:
if ([string]::IsNullOrWhiteSpace($owner)) {
throw "Owner is null or empty.";
}
If you have this embedded in a try block, you can catch the exception with the associated catch blocks. See Get-Help about_Try_Catch_Finally. You can also use Trap, I believe (See Get-Help about_Trap).
Finally, the default action when an error is encountered is controlled by the $ErrorActionPreference variable. That variable's default value is Continue, so error messages will be displayed but the script will continue executing as though no error happened at all. I'm not entirely sure how this works with manually thrown exceptions and try/catch blocks, but unless I know that I want my script to ignore errors, I start just about every script with:
$ErrorActionPreference = Stop;
See Get-Help about_Preference_Variables and Get-Help about_CommonParameters for more about this one.
Consider the following dataset. Note the null for Last_Name for one of the columns.
user_name first_name last_name
--------- ---------- ---------
lrivera0 Lawrence Rivera
tlawrence1 Theresa Lawrence
rboyd2 Roy
cperry3 Christine Perry
jmartin4 Jessica Martin
So if we want to be sure to only process full rows then a simple If would cover that.
Import-Csv .\text.csv | ForEach-Object{
If($_.Psobject.Properties.Value -contains ""){
# There is a null here somewhere
Throw "Null encountered. Stopping"
} else {
# process as normal
}
}
Problem is that Import-CSV treats nulls as zero length strings. I tried using -contains on just $_ but it did not work as $_ is not an array but an object with properties. So I used the object properties value to perform the comparison against.
Bacon brought up an interesting point in that this code would not account for whitespace only empty values.
We use throw so processing stops if a null is encountered. Using that if block you can do whatever action you want.

Resolve DNS, export to Excel and HTML, then send mail

I was almost done with my script and did some late night editing and written over my old version so I cannot go back.
The script was running fine, still needed some tweaks but now it ha come to a complete halt.
The idea is to GC a list of IP's. Resolve the IP's and place them in an excel sheet. Then save the sheet to htm and xlsx. And finally mailing those to me.
Now it gets stuck on sorting the sheet, saving AND mailing...
Can someone give me some insight on what I did wrong here?
it gets stuck on sorting the sheet, saving AND mailing.
It no longer sorts B3:B$Count:
Exception calling "Sort" with "1" argument(s): "The sort reference is not valid. Make sure that it's within the data you want to sort, and the first Sort By box isn't the same or blank."
At C:\Folder\Scripts\Get-IP.ps1:137 char:5
+ [void] $objRange.Sort($objRange2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
It no longer saves the xlsx file, but does save the HTM file. It is clearly not overwriting something. I even restarted to make sure.
Exception calling "SaveAs" with "1" argument(s): "Microsoft Excel cannot access the file 'C://Folder/BlockedIP/HTML/2014-07-08/0BCEF810'. workbook."
At C:\Folder\Scripts\Get-IP.ps1:160 char:5
+ $b.SaveAs("$FileXML")
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
And finally, it will no longer send me e-mails:
New-Object : Exception calling ".ctor" with "2" argument(s): "The specified string is not in the form required for an e-mail address."
At C:\Folder\Scripts\Get-IP.ps1:217 char:13
+ $SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
The script:
#Get current date
$Date = get-date -format yyyy-MM-dd
#Define all files/Paths.
$Path = "C:/Folder/BlockedIP"
md "$Path/HTML/$Date" -Force
$path2 = "$Path/HTML/$Date"
$PathWeb = "/HTML/$Date"
#Define File's used or created in this script.
$File = "$Path/IP-$Date.txt"
$FileHtml = "$Path2/IP-$Date.htm"
$FileXML = "$Path2/IP-$Date.xlsx"
$FileHTMLWeb = "$PathWeb/IP-$date.htm"
#Get content from given IP list.
$colComputers = #(get-content $File | Sort -unique)
$count = $colComputers.Count
write-output "$Count IP's detected."
#Define error actions.
#$erroractionpreference = "SilentlyContinue"
#Open Excel.
$a = New-Object -comobject Excel.Application
#Since we want this script to look like it's being used without excel I set it's visibility to false.
$a.visible = $True
#Disable excel confirmations.
$a.DisplayAlerts = $False
#Create sheets in Excel.
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
#Create a Title for the first worksheet and adjust the font
$row = 1
$Column = 1
target="_parent">Creator'
$c.Cells.Item($row,$column)= "Blocked IP's $Date"
$c.Cells.Item($row,$column).Font.Size = 18
$c.Cells.Item($row,$column).Font.Bold=$True
$c.Cells.Item($row,$column).Font.Name = "Cambria"
$c.Cells.Item($row,$column).Font.ThemeFont = 1
$c.Cells.Item($row,$column).Font.ThemeColor = 4
$c.Cells.Item($row,$column).Font.ColorIndex = 55
$c.Cells.Item($row,$column).Font.Color = 8210719
$range = $c.Range("a1","e1")
$range.Merge() | Out-Null
$range.VerticalAlignment = -4160
#Define subjects.
$c.Name = "Blocked IP's ($Date)"
$c.Cells.Item(2,1) = "Given IP"
$c.Cells.Item(2,2) = "Resolved DNS"
$c.Cells.Item(2,3) = "Returned IP"
$c.Cells.Item(2,5) = "Company name"
#Define cell formatting from subjects.
$c.Range("A2:E2").Interior.ColorIndex = 6
$c.Range("A2:E2").font.size = 13
$c.Range("A2:E2").Font.ColorIndex = 1
$c.Range("A2:E2").Font.Bold = $True
#Define the usedrange for autofitting.
$d = $c.UsedRange
#Make everything fit in it's cell
$D.EntireColumn.AutoFit() | Out-Null
#Define html code for Excel save to .htm.
$xlExcelHTML = 44
#Define rows to alter in excel.
$iRow = 3
$intRow = 3
#Time to run the script.
foreach ($strComputer in $colComputers)
{
#Place IP's from text in the excel sheet
$c.Cells.Item($intRow, 1) = $strComputer.ToUpper()
$d.EntireColumn.AutoFit() | Out-Null
#Create a status bar for the script
$i = 1
Write-Progress -Activity `
"Creating a usable 'Blocked IP' list ($i/$count)" `
-PercentComplete ($i/$colComputers.Count*100) `
-Status "Please stand by"
try {
$dnsresult = [System.Net.DNS]::GetHostEntry($strComputer)
}
catch {
$dnsresult = "$null"
}
#Clear screen on every checked IP to remove the 'True' statement.
#cls
#Do something with $dnsresults.
#Display information about host
#Give hostname Entry in Cell2
$c.Cells.Item($intRow,2) = $dnsresult.HostName
#IP listed in Cell 3
$c.Cells.Item($intRow,3) = $dnsresult.AddressList[0].IpAddressToString
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Define row for the IP list.
$intRow = $intRow + 1
#Set background color for the IP list.
$d.Range("A$($iRow):E$($intRow)").interior.colorindex = 15
#Sort all IP's on resolved name.
$objWorksheet = $b.Worksheets.Item(1)
$objRange = $objWorksheet.UsedRange
$objRange2 = $objworksheet.Range("B3:B($Count)")
[void] $objRange.Sort($objRange2)
#Define borders here.
<# Insert script :D #>
#Define Filters here. (Picking out blank DNS and giving those a name)
<# Insert script :D #>
#Define Filters here. (Picking out specific DNS name and give them color code)
<# Insert script :D #>
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Clear screen on every checked IP to remove the 'True' statement.
#cls
}
#Save the file as .xlsx on every placed IP to ensure the file is not lost due to any reason.
$b.SaveAs("$FileXML")
#Save final result as a .htm file
$b.SaveAs("$FileHTML",$xlExcelHTML)
#Close and quit Excel.
$b.Close()
get-process *Excel* | Stop-Process -force
#Move .txt file to the correct HTML folder.
move-item $file $path2 -Force
#Clear screen, again. (Let's keep things tidy.)
#cls
#Variables for public IP
# I am defining website url in a variable
$url = "http://checkip.dyndns.com"
# Creating a new .Net Object names a System.Net.Webclient
$webclient = New-Object System.Net.WebClient
# In this new webdownlader object we are telling $webclient to download the
# url $url
$IpPublic = $webclient.DownloadString($url)
# Just a simple text manuplation to get the ipadress form downloaded URL
# If you want to know what it contain try to see the variable $IpPublic
$IpPublic2 = $IpPublic.ToString()
$ipPublic3 = $IpPublic2.Split(" ")
$ipPublic4 = $ipPublic3[5]
$ipPublic5 = $ipPublic4.replace("</body>","")
$FinalIPAddress = $ipPublic5.replace("</html>","")
#Variables e-mail.
$From = "Blocked IP <###g##.com>"
$To = "IT Dept <#####.nl>"
$CC = "Someone <##r###.nl"
$SMTP = "smtp.gmail.com"
$Subject = "Blocked IPs for $date ($Count Total)"
#The href should point to the htm file in your iis/apache folder.
$WebLink = $FinalIPAddress+$FileHtmlWeb
$here = "<a href='http://$Weblink'><b>Here</b></a>"
#Define the body of your e-mail, in this case it displays a message and shows the server it is send from with it's local IP.
#A link to the .htm file, how many IP's were blocked and the date of the message.
$Body = "This is an automated message generated by server: $env:COMPUTERNAME, $IP</br></br>
Please see the attachment or click $here to get the $Count blocked IP's of $date. </br> </br></br>"
#Variables e-mail user.
$username = "#####.com"
$password = "##"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
$ip = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]
#Clear screen, again. (Let's keep things tidy.)
#cls
#Send output as e-mail.
$SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential("$username", "$password");
$SMTP.isbodyhtml= $true
$SMTP.Send($From, $To, $Subject, $FileXML, $Body)
send-mailmessage -BodyAsHtml -from $From -to $To -cc $CC -subject $Subject -Attachments $FileXML -body $Body -priority High -smtpServer $SMTP -credential ($cred) -usessl
#Create a function to relase Com object at end of script.
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject(
[System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
#Release COM Object
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$a) | Out-Null
#Clear screen for the final time. (Let's keep things tidy.)
#cls
#Exit powershell
exit
Any help will be greatly appreciated!
Exception calling "Sort" with "1" argument(s): "The sort reference is not valid. Make sure that it's within the data you want to sort, and the first Sort By box isn't the same or blank."
Double-check that $objRange and $objRange2 reference the correct ranges:
$objRange.Address()
$objRange2.Address()
Not much else I can tell you here without seeing your actual data.
Exception calling "SaveAs" with "1" argument(s): "Microsoft Excel cannot access the file 'C://Folder/BlockedIP/HTML/2014-07-08/0BCEF810'. workbook."
If the path really were C://Folder/... you'd be getting a different exception. Please do not fabricate error messages.
New-Object : Exception calling ".ctor" with "2" argument(s): "The specified string is not in the form required for an e-mail address."
You're confusing MailMessage and SmtpClient class. Not to mention that you don't even need either of them, since you're using Send-MailMessage anyway. Just remove the following 5 lines:
$SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential("$username", "$password");
$SMTP.isbodyhtml= $true
$SMTP.Send($From, $To, $Subject, $FileXML, $Body)
Solved the sorting problem by altering the code to:
$objRange = $c.Range("A$($iRow):E$($intRow)")
$objRange2 = $c.Range("B$($iRow):B$($intRow)")
[void] $objRange.Sort($objRange2)
Turns out it got stuck on the header in the xml file
Replaced mailing with:
$From = "Blocked IP <#####.##>"
$To = "IT Dept <#####.##>"
$CC = "Someone <#####.##"
$Subject = "Blocked IPs for $date ($Count Total)"
#The href should point to the htm file in your iis/apache folder.
$WebLink = $FinalIPAddress+$FileHtmlWeb
$here = "<a href='http://$Weblink'><b>Here</b></a>"
#Define the body of your e-mail, in this case it displays a message and shows the server it is send from with it's local IP.
#A link to the .htm file, how many IP's were blocked and the date of the message.
$body = "Dear <font color=black>$to</font>,<br><br>"
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
$Username = "###gmail.com"
$Password = "##"
$message = New-Object System.Net.Mail.MailMessage
$message.IsBodyHTML = $true
$message.ReplyTo = $From
$message.Sender = $From
$message.subject = $subject
$message.body = $body
$message.to.add($to)
$message.from = $username
$message.attachments.add($MailXML)
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
The saving of the file turned out to be a misguided path.
Since the excel sheet was created from folder1 it wouldnt save instandly to folder 2 since its temp save file would remain in folder1.