Categories

Windows 7

Transferring modules from Windows 8 or 8.1 to Windows 7

Windows 7 shipped with PowerShell 2.0 installed.  Windows 8 brought PowerShell 3.0 and Windows 8.1 brings PowerShell 4.0.

 

Windows 8 and 8.1 also have a lot of modules installed. This extra functionality widens PowerShell reach immensely – the networking modules alone are a significant step forward.

When you install PowerShell 3.0 or 4.0 on Windows 7 you don’t most of the new modules. This has puzzled many people and I’m often asked how those Windows 8/8.1 modules can be made available on Windows 7.

The short answer is that you can’t.

The long answer is that you can’t because, for the most part, those modules are based on CIM (WMI) classes that were introduced in Windows 8 or 8.1. A lot of the system management functionality you see in modern Windows is based on CIM classes that then use the CDXML approach to create PowerShell modules.

Installing the new CIM classes on Windows 7 is not possible – so you can’t get the modules on which they are based.

If you want the new functionality you have to upgrade to Windows 8.1

PowerShell v3 installed modules

This is the list of installed modules in PowerShell v3 on Windows 8

AppLocker
Appx
BitLocker
BitsTransfer
BranchCache
CimCmdlets
DirectAccessClientComponents
Dism
DnsClient
International
iSCSI
ISE
Kds
Microsoft.PowerShell.Diagnostics
Microsoft.PowerShell.Host
Microsoft.PowerShell.Management
Microsoft.PowerShell.Security
Microsoft.PowerShell.Utility
Microsoft.WSMan.Management
MMAgent
MsDtc
NetAdapter
NetConnection
NetLbfo
NetQos
NetSecurity
NetSwitchTeam
NetTCPIP
NetworkConnectivityStatus
NetworkTransition
PKI
PrintManagement
PSDiagnostics
PSScheduledJob
PSWorkflow
PSWorkflowUtility
ScheduledTasks
SecureBoot
SmbShare
SmbWitness
Storage
TroubleshootingPack
TrustedPlatformModule
VpnClient
Wdac
WebAdministration
WindowsDeveloperLicense
WindowsErrorReporting

This is the corresponding list on PowerShell v3 installed on Windows 7

AppLocker
BitsTransfer
CimCmdlets
ISE
Microsoft.PowerShell.Diagnostics
Microsoft.PowerShell.Host
Microsoft.PowerShell.Management
Microsoft.PowerShell.Security
Microsoft.PowerShell.Utility
Microsoft.WSMan.Management
PSDiagnostics
PSScheduledJob
PSWorkflow
PSWorkflowUtility
TroubleshootingPack

As you can see there is quite a difference!

All of the Windows 8 modules that are highlighted in yellow are CDXML based.  They can’t be made available on Windows 7 because the underlying WMI classes aren’t available

PowerShell 3 and Word

 

This is a common scenario

$word = New-Object -ComObject "Word.application"            
$word.visible = $true            
$doc = $word.Documents.Add()            
$doc.Activate()            
            
$word.Selection.Font.Name = "Cambria"            
$word.Selection.Font.Size = "20"            
$word.Selection.TypeText("PowerShell")            
$word.Selection.TypeParagraph()            
            
$word.Selection.Font.Name = "Calibri"            
$word.Selection.Font.Size = "12"            
$word.Selection.TypeText("The best scripting language in the world!")            
$word.Selection.TypeParagraph()            
            
$file = "c:\scripts\office\test1.doc"            
$doc.SaveAs([REF]$file)            
            
$Word.Quit()


Create a new Word document – put some text into it and save it with a given file name.  I’ve used it successfully to create server documentation.



Unfortunately with PowerShell v3 it fails with this message



 



Exception calling "SaveAs" with "1" argument(s): "This is not a valid file name.

Try one or more of the following:


* Check the path to make sure it was typed correctly.


* Select a file from the list of files and folders."


At line:17 char:1


+ $doc.SaveAs([REF]$file)


+ ~~~~~~~~~~~~~~~~~~~~~~~


    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException


    + FullyQualifiedErrorId : COMException



It appears not to like the [ref] but if you leave it out you get this



Argument: '1' should be a System.Management.Automation.PSReference. Use [ref].

At line:18 char:1


+ $doc.SaveAs($file)


+ ~~~~~~~~~~~~~~~~~~


    + CategoryInfo          : NotSpecified: (:) [], MethodException


    + FullyQualifiedErrorId : NonRefArgumentToRefParameterMsg



[ref] isn’t case sensitive.



The only way round it that I know of is to create a blank Word document to use as a template



Copy-Item -Path mydoc.doc  -Destination testdoc.doc -Force            
            
$file = "C:\MyData\SkyDrive\Data\Scripts\Office-Word\testdoc.doc"            
            
$word = New-Object -ComObject "Word.application"            
$word.visible = $true            
$doc = $word.Documents.Open($file)            
            
$word.Selection.Font.Name = "Cambria"            
$word.Selection.Font.Size = "20"            
$word.Selection.TypeText("PowerShell")            
$word.Selection.TypeParagraph()            
            
$word.Selection.Font.Name = "Calibri"            
$word.Selection.Font.Size = "12"            
$word.Selection.TypeText("The best scripting language in the world!")            
$word.Selection.TypeParagraph()            
            
$doc.Save()            
$doc.Close()            
$Word.Quit()


Notice that you need to give the full path to the file. Use the Open method and add the text. You can then save, close and quit the application.



I’ve tested this using office 2010 & office 2013 on Windows 7 & 8



Unfortunately we are still left with the problem that we can’t save the Word document into different formats.

Remoting between PowerShell v3 CTP 2 and PowerShell v2

One of the questions on tonight’s Live Meeting concerned the compatibility between remoting on PowerShell v2 and PowerShell v3 CTP 2

The difference is that v3 uses a WSMAN 3.0 stack but v2 uses 2.0

I used two machines:

  • Windows 2008 R2 SP 1 with PowerShell v2
  • Windows 7 SP1 with PowerShell v3 CTP 2

 

on each machine I ensured remoting was enabled then ran

$s = New-PSSession –ComputerName <other computer name>
Invoke-Command -Session $s -ScriptBlock {get-service}

 

it worked in both cases

Looks like in this case you can remote both ways

Outlook: deleting mail items

As promised here is the function to delete mail items in a specific folder

function remove-mailitem {            
[CmdletBinding(SupportsShouldProcess=$true)]            
param (            
 [parameter(Mandatory=$true)]            
 [string]$mailfolder,            
              
 [datetime]$start,            
 [datetime]$finish            
)            
if ($start -and $finish){            
  if ($start -ge $finish){Throw "Finish $($finish) before Start $($start)"}            
}            
            
$outlook = New-Object -ComObject Outlook.Application            
            
$folders | where {$_.Path -like "*$mailfolder"} |            
foreach {            
$targetfolder = $outlook.Session.GetFolderFromID($_.EntryID, $_.StoreID)            
              
foreach ($item in $targetfolder.Items)  {            
 if ($start){if ($item.SentOn -lt $start){continue}}            
 if ($finish){if ($item.SentOn -gt $finish){continue}}            
             
  if ($psCmdlet.ShouldProcess("$($item.Subject) $($item.SentOn)", "deleting")) {            
    $item.Delete()            
  }            
              
}            
               
}            
}


If you’ve been following this series you will recognise how this works. The parameters are the mail folder and an optional start and finish date. A quick check to see if the dates are the right way round and we jump into the processing. The folders that match our input are selected and foreach of them we loop through the mail items.



The SentOn date is compared to the start and finish dates if they are defined. The item is skipped if it falls outside of those dates.



We can then delete the item. I’ve also added the –whatif parameter to the function by using   [CmdletBinding(SupportsShouldProcess=$true)] and



if ($psCmdlet.ShouldProcess("$($item.Subject) $($item.SentOn)", "deleting")) {
  $item.Delete()
}



This adds another safety level to the function

Outlook: Viewing mail items

Continuing our perambulation around Outlook when used with multiple hotmail accounts its time to look at the other folders and the mail items they contain. This post will show how to view the mail items and a future post will show how to delete items from a specific folder.

Viewing mail items cam be performed with the following function

function get-mailitem {            
param (            
 [parameter(Mandatory=$true)]            
 [string]$mailfolder,            
              
 [switch]$all            
)            
$outlook = New-Object -ComObject Outlook.Application            
            
$folders | where {$_.Path -like "*$mailfolder"} |            
foreach {            
  $targetfolder = $outlook.Session.GetFolderFromID($_.EntryID, $_.StoreID)            
              
  if ($all){            
   $targetfolder.Items             
  }            
  else {            
   $targetfolder.Items | sort  SentOn -desc | select SenderEmailAddress, Subject, SentOn            
  }            
}            
}


The function takes a mandatory string to identify the mail folder and we use the GetFolderFromID method to access the folder. If the –all switch is used we get a dump of the full object for each mail item otherwise a summary consisting of the sender’s address, date sent and subject is output.



If the –all switch is used we can use the PowerShell pipeline to perform further processing e.g.



get-mailitem –mailfolder Baen –all | where {$_.SentOn –lt [datetime]”1 January 2007”}

Clearing junk mail

Getting back to looking at working with Outlook we can adapt the function used to deleted the contents of the Deleted Items folder to work with the Junk mail folders

function clear-junkmail {            
            
$outlook = New-Object -ComObject Outlook.Application            
            
get-mailitemcount -junk            
            
$folders | where {$_.Path -like "*junk*"} |            
foreach {             
  $mailfolder = $outlook.Session.GetFolderFromID($_.EntryID, $_.StoreID)            
              
  if ($mailfolder.Items.Count -gt 0){            
    do {            
      foreach ($item in $mailfolder.Items){$item.Delete()}            
    } while ($mailfolder.Items.Count -gt 0)            
  }               
}            
get-mailitemcount -junk            
}


Very much the same as before but we are looking for folders that contain the work “junk”



It would be possible to combine this function and the clear-deletedmail function but I decided to keep them separate for simplicity

Outlook folder item count revisited

I started this series http://msmvps.com/blogs/richardsiddaway/archive/2011/07/30/outlook-connector-amp-mail-folder-item-count.aspx by looking at how we could enumerate the mail folders in Outlook 2010 when I had had four hotmail accounts open.  The function has been modified since then

function get-mailitemcount {            
param (            
 [parameter(ParameterSetName="All")]            
 [switch]$all,             
 [parameter(ParameterSetName="Deleted")]            
 [switch]$deleted,            
 [parameter(ParameterSetName="Junk")]            
 [switch]$junk            
)            
$outlook = New-Object -ComObject Outlook.Application            
foreach ($folder in $outlook.Session.Folders){            
              
  foreach($mailfolder in $folder.Folders ) {            
               
  if ($deleted) {if ($($mailfolder.Name) -notlike "Deleted*"){continue} }            
  if ($junk)  {if ($($mailfolder.Name) -notlike "Junk*"){continue} }            
               
    New-Object -TypeName PSObject -Property @{            
      Mailbox = $($folder.Name)            
      Folder = $($mailfolder.Name)            
      ItemCount = $($mailfolder.Items.Count)            
    } | select Mailbox, Folder, ItemCount            
  }             
}            
}


I use three parameters to determine if I want to look at the Junk or Deleted folders or if I’m  going to dump the information for all folders.  I’ve used parameter sets to make the three parameters mutually exclusive.



The same loops are used as before – the outer one loops through the mailboxes and the inner one through each folder in those mailboxes.  If the deleted or junk switches have been set any folder that doesn’t match the criteria is skipped.



An object is created to output the name of the mailbox, folder and number of items

Emptying the Deleted folders–version 2

The original version of this function only did one pass at deleting and had to iterate through all of the folders to find the Deleted Items folder.  This time we use the collection of folders we created using the get-mailfolders function to go directly to the Deleted Items folders

function clear-deletedmail {            
            
$outlook = New-Object -ComObject Outlook.Application            
            
get-mailitemcount -deleted            
            
$folders | where {$_.Path -like "*deleted*"} |            
foreach {             
  $mailfolder = $outlook.Session.GetFolderFromID($_.EntryID, $_.StoreID)            
              
  if ($mailfolder.Items.Count -gt 0){            
    do {            
      foreach ($item in $mailfolder.Items){$item.Delete()}            
    } while ($mailfolder.Items.Count -gt 0)            
  }               
}            
get-mailitemcount -deleted            
}


The function starts by creating an Outlook object. The get-mailitemcount function has been revised to provide just the deleted or junk folders (I’ll post about the revisions next)



The folders collection is filtered to select just the Deleted Items folders (I’m assuming the word deleted hasn’t been used in any other folder names).  Each of the Deleted Items folders is accessed using the GetFoderFromID function using the EntryID and StoreID saved in the objects in the folder. If the number of items in the folder is not zero we create a do loop to control the deletion. This will keep calling the foreach loop that performs the deletion until everything is deleted (remember our original function need several passes to delete all the items in the folder)



A final call to get-mailitemcount shows the position after all of the items are deleted.

Outlook folders

The functions we’ve seen so far have involved iterating through the whole set of Outlook folders. That’s a lot of folders (I have 4 email accounts with lost of folders). The trick is to do this just once and then use the GetFolderFromID method at the Outlook.Session level to access individual mailboxes

The problem is that this method uses the EntryID and the StoreID for the folder.  Examples are shown below

StoreID : 0000000038A1BB1005E5101AA1BB08002B2A56C200006D736E636F6E2E646C6C
0000000000000000E92FEB759650448683B87DE522AA494800433A5C55736572735C526963
686172645C417070446174615C4C6F63616C5C4D6963726F736F66745C4F75746C6F6F6B5C
726963686172645F73696464617761795F686F746D61696C2E6F737400

EntryID : 000000000A9D24A8D4E32445825D043ADB203A6F0100D9539C2261A6BB45B9DA
B62C7081B3C101002100FFFF0000

Path    : \\emailaddress@hotmail.com\Deleted Items

Now I for one don’t want to be typing in the EntryID never mind the StoreID – chances of getting that right range from zero to about … zero

What we can do convert all of our Outlook functions (the two so far and the others I’ve got planned) into a module. In the psm1 file I can put a line that says

$folders = get-mailfolders

That creates a collection of objects containing the path, storeid and entryid for all of the folders. We can then search through that collection much faster than the outlook folders.

We create the collection like this

function get-mailfolders {            
$outlookfolders = @()            
$outlook = New-Object -ComObject Outlook.Application            
foreach ($folder in $outlook.Session.Folders){            
              
  foreach($mailfolder in $folder.Folders ) {            
   $olkf = New-Object PSObject -Property @{            
    Path = $($mailfolder.FullFolderPath)            
    EntryID = $($mailfolder.EntryID)            
    StoreID = $($mailfolder.StoreID)            
   }            
               
   $outlookfolders += $olkf            
               
  }             
}            
$outlookfolders            
}


Loop through the folders as we’ve already seen and create an object that has the path, storeid and entryid. Add the object to the collection and output the collection at the end of the function.



We’ll see how to use this collection in the revised clear-deletedmail function