having some problems trying to figure this one out.
For some reason my script is not working as it should.
It should mark all mails in inbox folder as read and then delete them.
However, when the script runs it only delete's half of the .count $emails show...
How to solve this, am I doing something wrong?
$outlook = new-object -comobject outlook.application
#Define folders
$namespace = $outlook.GetNameSpace("MAPI")
$pst = $namespace.Stores
$pstRoot = $pst.GetRootFolder()
$pstFolders = $pstRoot.Folders
#$personal = $pstFolders.Items("ARCHIVE") ##Not working, sadly.
$DefaultFolder = $namespace.GetDefaultFolder(6)
$InboxFolders = $DefaultFolder.Folders
$DeletedItems = $namespace.GetDefaultFolder(3)
$Emails = $DefaultFolder.Items
Foreach ($Email in $Emails) {
#Define folders
$Email.UnRead = $false
$Email.Move($DeletedItems) | out-null
continue
}
I would suggest using the MailItem.Delete() method instead of moving things to the Deleted Items folder. From the Delete() method page:
The Delete method deletes a single item in a collection. To delete all
items in the Items collection of a folder, you must delete each item
starting with the last item in the folder. For example, in the items
collection of a folder, AllItems, if there are n number of items in
the folder, start deleting the item at AllItems.Item(n), decrementing
the index each time until you delete AllItems.Item(1).
The Delete method moves the item from the containing folder to the
Deleted Items folder. If the containing folder is the Deleted Items
folder, the Delete method removes the item permanently.
With that knowledge I would suggest replacing your ForEach loop with the following:
For($i=($emails.count-1);$i -ge 0;$i--){
$($emails)[$i].Unread = $false
$($emails)[$i].delete()
}
I don't understand why you have to sub-expression the collection in order to enumerate it, but I've never been able to specify a record without doing that.
Do not use "foreach" loop since you are modifying the number of items in the collection. Use a loop from Items.Count down to 1.
Related
Basically I'm trying to create a ContextMenu that shows a list of mounted network drive, so the user can click on one and access it.
The problem is : the list is not fix, but depend on a string list extracted from a DataGrid.
In my mind, I could just create the menu items by looping on that string list.
Here is the code sample :
$Main_Tool_Icon = New-Object System.Windows.Forms
[...]
# Add menu entries
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
foreach ($nameTag in $dataGrid[$DATAGRID_COLUMN_NAME]){
$menuEntry = New-Object System.Windows.Forms.MenuItem
$menuEntry.Text = $nameTag
$Main_Tool_Icon.ContextMenu.MenuItems.AddRange($menuEntry)
}
# Add separator
$Main_Tool_Icon.ContextMenu.MenuItems.Add('-')
# Last one to show main form
$displayMainform = New-Object System.Windows.Forms.MenuItem
$displayMainform.Text = "Edit"
$Main_Tool_Icon.ContextMenu.MenuItems.Add($displayMainform)
It appears that's wrong and only shows the separator and last "Edit" line.
Is there a way to populate a contextual menu with a list ?
Or I should look for a better way to do the job ?
EDIT #Mathias R. Jessen
You're right about AddRange(), it's just a mistake.
I can't show everything since it's quite big. I'll try to synthesize an answer properly.
The data grid is created/populated by my script and is just a table of strings from a csv file.
$DATAGRID_COLUMN_NAME = 'NAME' it's one of constants defined for my datagrid headers.
Here is the setup
# set datagrid
$dataGrid = New-Object System.Windows.Forms.DataGridView
[...]
$dataGrid.Columns.Add($DATAGRID_COLUMN_NAME,'Name')
$dataGrid.Columns[0].DataPropertyName = $dataGrid.Columns[0].Name
Then some databinding
# initiate the data table
$table = New-Object System.Data.DataTable
[...]loop on csv file[...]
# bind data table to data grid
$dataGrid.DataSource = $table
EDIT #SantiagoSquarzon
You were right for pointing out $dataGrid[$DATAGRID_COLUMN_NAME]. The syntax was bad and returned something null. Instead, I looped over the $dataGrid.Rows like so:
foreach ($row in $dataGrid.Rows){
$nameTag = $row.Cells[$DATAGRID_COLUMN_NAME].Value
if ($nameTag -ne $null){
$menuEntry = New-Object System.Windows.Forms.MenuItem
$menuEntry.Text = $nameTag
$Main_Tool_Icon.ContextMenu.MenuItems.AddRange($menuEntry)
}
}
Now I just have to figure out how to invoke there click event.
I have one excel file with a few sheets in it. Im trying to combine all of them into one sheet. I have code that does this, but the issue im having now is now the sheets have formulas on them. So when it copies to the new sheet it doesnt copy over the values, but copies the formulas. I was reading online you can do this pastespecial, but i cant get it working. Does anyone have code on how can i copy one sheet to another, but keep the values. The one issue is the sheets have differnt amount of rows so im not sure how what the range would be. I have tried many things and i just can figure it out.
This is using the excel.application
To questions, the thing is i cant figure out the .PasteSpecial.
Here is the code now that copies it to a sheet called combine.
$wb = $excel.Workbooks.Open($location)
$newSheetName = 'Combine'
$xlCellTypeLastCell = 11
$targetSheet = $wb.Sheets.Add()
$targetSheet.Name = $newSheetName
$includeHeader = $true
foreach ($sh in $wb.Sheets) {
$statename = $sh.Name
if($sh.name -eq "Available" -or $sh.name -eq "Eligible"){
}else{
$sh.autofiltermode = $false
$sourceRange = $sh.UsedRange
if ($sourceRange.Rows.Count -le 1) { continue }
# if ($sourceRange.Rows.Count -le 1 -or $sh.Name -eq $newSheetName) {continue}
$targetLastCell = $targetSheet.UsedRange.SpecialCells($xlCellTypeLastCell)
if ($includeHeader) {
[Void]$sourceRange.PasteSpecial($targetLastCell)
}
else {
$columnOffset = - $targetLastCell.Column + 1
$targetCell = $targetLastCell.Offset(1, $columnOffset)
$newRowCount = $sourceRange.Rows.Count - 1
[Void]$sourceRange.Offset(1, 0).Resize($newRowCount).Copy($targetCell)
}
$includeHeader = $false
}
}
This does work with headers, but at this point i dont really care about those. I will say UsedRange also misses me up. So right now it i just use the .copy and from reading online i need to use the pasteSpecial.
I have word files in folder D:\xxx.docx, There are images in it but those are externally linked.
Now i want to save the docx file with Break link so that the document becomes document with embedded images.
I Found some code but not sure how to put it properly , can any one help please
$wrd = New-Object -ComObject "Word.Application"
$doc = $wrd.Documents.Open('C:\test.rtf')
$opt = [ref][Microsoft.Office.Interop.Word.WdSaveFormat]::WdFormatRTF
$name= [ref]'C:\test.rtf'
$wrd.ActiveDocument.SaveAs($name, $opt)
$wrd.ActiveDocument.Close()
$wrd.Quit()
$images = $doc.InlineShapes
foreach ($image in $images) {
$linkFormat = $image.LinkFormat
$linkFormat.SavePictureWithDocument = 1
$linkFormat.BreakLink()
}
As per my comment. See this SO Q&A similar to your use case:
Replacing all occurrences of a string in a Word file by an hyperlink
Though the OP there is talking about replacement, it's the same approach for removal.
I will be executing a script to remove permissions from a SPitem. However, a rollback plan is required and I am required to create a separate script which will add the permission of the user back to the SPitem if required.
Below is my code snippet which removes a user from the SPitem:
ForEach ($RDfolderId in $RDfolderSplit)
{
$query = New-Object Microsoft.SharePoint.SPQuery
$query.ViewXml = "#<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='Title' /><Value Type='Text'>$RDfolderId</Value></Eq></Where></Query></View>"
$RDfolder = $RDlist.GetItems($query)
foreach($role in $RDfolder.RoleAssignments)
{
if ($role.Member.Name.Equals($userToAction))
{
#$RDitem.BreakRoleInheritance($true)
#$RDitem.RoleAssignments.RemoveById($roleAssignment.Member.ID)
#$RDitem.Update()
}
}
}
I have seen code samples online on adding roles back to the SPitem. However, there is an additional field RoleDefinitions declared.
Is it compulsary to have the value declared when adding a user to a SPitem?
Below is the code sample for adding:
$web = Get-SPWeb http://sp-2010
$account = $web.EnsureUser("SHAREPOINT\mray")
$role = $web.RoleDefinitions["Contribute"] #is this value compulsory?
$list = $web.Lists["Shared Documents"]
$list.BreakRoleInheritance($true)
$assignment = New-Object Microsoft.SharePoint.SPRoleAssignment($account)
$assignment.RoleDefinitionBindings.Add($role)
$list.RoleAssignments.Add($assignment)
$list.Update()
$web.Dispose()
source
Short answer - Yes.
Let's break this sample up and explain each part:
$web = Get-SPWeb http://sp-2010
$web - SharePoint Web object aka. Site we are working on.
$account = $web.EnsureUser("SHAREPOINT\mray")
$account - User account we are working with.
$role = $web.RoleDefinitions["Contribute"] #is this value compulsory?
$role - This is the Role Definition aka permissions like Contribute/Read/Approve. Yes. This is mandatory as it is the permissions you are going to add back.
$list = $web.Lists["Shared Documents"]
$list - The List we are working with.
$list.BreakRoleInheritance($true)
BreakRoleInheritance - This is if we need unique permissions on the List and to turn inheritance off. We don't have to do this every time, and likely in this example, you don't have to break inheritance.
Now, we are onto the permissions pieces.
$assignment = New-Object Microsoft.SharePoint.SPRoleAssignment($account)
$assignment - First, we need to get all the SharePoint roles currently assigned to our user.
$assignment.RoleDefinitionBindings.Add($role)
Add($role) - Add the Role Definition i.e. "Contribute" to the user object. This does nothing to the list on SharePoint.
$list.RoleAssignments.Add($assignment)
Add($assignment) - Add user with the new permissions to the List object. This does nothing to the list on SharePoint. We are manipulating the end state of the list that we want.
$list.Update()
Update - Now do something on SharePoint. Actually apply the changes we have made to the List object to SharePoint.
$web.Dispose()
Dispose - cleanup our objects.
Now. Saying all of that. This is a good script for setting permissions. You also have a script for removing permissions. The point of a rollback script is that you need to record what those permissions originally were before you remove them. i.e. once you remove them, there isn't a magic undo button. ;-)
My Goal:
Create a new tab and name it within a function.
My Issue:
When I create the new tab inside of the function I cannot name the tab.
Notes:
When I create the tab outside of the function it gets named correctly.
My Code:
Function Function_CreateWorksheets($Worksheets) {
ForEach ($Worksheet in $Worksheets) {
$Excel_Count_Worksheet++
If ($Excel_Count_Worksheet -gt 3) {$Script:Excel.Worksheets.Add() |Out-Null}
$Script:Excel.Worksheets.Item($Excel_Count_Worksheet).Name = $Worksheet
}
}
#Load-Module -Module grouppolicy -Reload
Get-Variable Excel_* |Remove-Variable -Force
#Create Excel Com Object
$Excel = New-Object -com Excel.Application
# Make the Excel Application Visible to the end user
$Excel.visible = $True
# Create a WorkBook inside the Excel application
# that we can start manipulating.
$Excel_Workbook = $Excel.Workbooks.Add()
$Action_CreateWorksheet =
#=======================================================
# Now that we have a workbook we need to create some
# additional worksheets (Tabs) beyond that initial 3
# that are created when the workbook is opened.
#=======================================================
#$Temp_MakeWorkSheet = $Excel.Worksheets.Add()
#=======================================================
# Once all the sheets are created each of the worksheets
# need to be assigned to a variable so they can be
# manipulated.
#=======================================================
Function_CreateWorksheets -Worksheets "System Summary","Test1","Test2","Test3","Test4"
The problem is that you're assuming that the new worksheet is added as the last worksheet, but by default the Add method adds the new worksheet at the beginning. The script as you have it works for up to three worksheets, but after that it adds new worksheets with default names at the beginning and keeps renaming the last worksheet. You need to add the worksheets at the end.
Change
$Script:Excel.Worksheets.Add()
to
$Script:Excel.Worksheets.Add([System.Reflection.Missing]::Value, $Script:Excel.Worksheets.Item($Script:Excel.Worksheets.Count))
How that works:
The Add method takes four arguments: Before, After, Number, and Type.
$Excel.Worksheets.Item($Excel.Worksheets.Count)) gets the object representing the last worksheet. You want to supply that as the After argument in order to add the new worksheet after the last worksheet.
[System.Reflection.Missing]::Value is a placeholder for the missing Before argument (it can't be null)
APPENDIX
As an afterthought...although it works for this specific script, I find it a little iffy to rely on the default initial configuration of three worksheets, have the function change behavior based on that assumption (rename three worksheets, then start adding more), and rely on a counter variable to determine which worksheet you're renaming rather than renaming the active sheet you just added. Since the function inherits a preexisting workbook rather than creating a new one, I think it's "cleaner" to write a function that will give you a workbook that has blank worksheets with the specified names regardless of the initial configuration.
Maybe it's just my personal philosophical inclination to write functions in ways that are more generally applicable (read: reusable) rather than ad hoc, but I prefer functions that make as few assumptions as possible. Here's how I'd do it:
function Function_CreateWorksheets {
[CmdletBinding()]
param (
[string[]] $WorkSheets,
[object] $Excel
)
for ($i = 1; $i -le $Excel.Worksheets.Count; $i++) {
$Excel.Worksheets.Item($i).Name = "DeleteMe$i"
}
foreach ($Worksheet in $Worksheets) {
$Excel.Worksheets.Add([System.Reflection.Missing]::Value,$Excel.Worksheets.Item($Excel.Worksheets.Count)) | Out-Null
$Excel.ActiveSheet.Name = $Worksheet
}
$Excel.DisplayAlerts = $false
while ($Excel.Worksheets.Count -gt $Worksheets.Count) {
$Excel.Worksheets.Item(1).Delete()
}
$Excel.DisplayAlerts = $true
}
The while loop at the end deletes all the preexisting worksheets. All new worksheets are added at the end, so the loop removes worksheets from the beginning until the number of worksheets in the workbook ($Excel.Worksheets.Count) matches the number of new worksheets ($Worksheets.Count). This needs to come at the end because a workbook has to have at least one worksheet, so you'll get an error if you try to delete all the worksheets before creating any new ones.
The initial for loop renames all the preexisting worksheets so that the function won't break if any of the new names match names that are already in use. You're going to get rid of them anyway, so you don't care what their names are.
By passing the Application object to the function as an argument, you can avoid the need to keep scoping it. You can call the function like this:
Function_CreateWorksheets -Worksheets "System Summary","Test1","Test2","Test3","Test4" -Excel $Excel
(Of course, you can leave off the parameter names -Worksheets and -Excel as long as you maintain the correct order.)
Thanks to your post I was able to get the result I needed. I did a slight variation to make the function reusable.
Function Function_CreateWorksheets {
[CmdletBinding()]
param (
[parameter(Mandatory=$true,ValueFromPipeline=$true)][object] $Excel,
[string[]] $WorkSheets
)
ForEach ($Worksheet in $Worksheets) {
$Script:Excel_Count_Worksheet++
If ($Excel_Count_Worksheet -gt $Excel.Worksheets.Count) {$Excel.Worksheets.Add([System.Reflection.Missing]::Value, $Excel.Worksheets.Item($Script:Excel.Worksheets.Count)) |Out-Null}
$Excel.Worksheets.Item($Excel_Count_Worksheet).Name = $Worksheet
}
While ($Excel.Worksheets.Count -gt $Script:Excel_Count_Worksheet) {
$Excel.Worksheets.Item($Excel.Worksheets.Count).Delete()
}
}