Run powershell commands on remote computers

Sometimes you need a quick and easy way to run powershell commands on multiple remote machines. This script will read a text file or a list of machines, ping them to ensure they are responding so you don’t waste time on offline machines, then run a remote command.

It times out the ping test at 5ms, which is good for local networks. You may need to increase for slow networks. It also does not establish a profile on the remote machine, which speeds up command processing.

$ScriptBlock = {
#Enter code here
Write-Output $env:computername
}

$computers = @('computer1','computer2')
#$computers = Get-Content c:\temp\computers.txt
$computercount = $computers.Count

foreach($computer in $computers){
    #First check if its even worth it
    $online = Test-Connection $computer -Count 1 -Quiet -TimeToLive 5

    if($online){Write-Output "$computer online with $($computercount) remaining to check"}
           else{Write-Warning "$computer offline with $($computercount) remaining to check"}

     $computercount = $computercount - 1
     
     if($online){
        #Uncomment if input is IP addresses
        #$ComputerName = $computer | ForEach-Object {([system.net.dns]::GetHostByAddress($_)).hostname}
        Write-Output "Found DNS entry $ComputerName for IP $computer"
        $session = New-PSSession -ComputerName $ComputerName -SessionOption (New-PSSessionOption -NoMachineProfile)
        $result = Invoke-Command -session $session -ScriptBlock $ScriptBlock
        Write-Output $result
        Remove-PSSession $session
    }
    else #not online
    {
    }
}

You can comment out the list of names and uncomment the text file to have it read a text file with a list of machine names.

You can uncomment the line that converts an IP address into a name if the input is IP addresses, since powershell won’t let you run against an IP address remotely.

This is also easy to convert into a function, which I’ll add when I have some more time later.

Download and install WMF5 with Powershell

Download and install Windows Management Framework 5.0 aka Powershell 5.0 using Powershell

<# 

Written By Eric Holzhueter with no implied guarantee it will work

This does not check for prerequisites, specifically the following note from MS

    Important Note: For systems running Windows 7 SP1 or Windows Server 2008 R2 SP1, you’ll need WMF 4.0 and .NET Framework 4.5 or higher installed to run WMF 5.0.

It also does not cleanup the file when done. Some might want this.

WMF 5.0 download page
    https://www.microsoft.com/en-us/download/details.aspx?id=50395

WMF Blog posts 
    https://blogs.msdn.microsoft.com/powershell/2015/12/16/windows-management-framework-wmf-5-0-rtm-is-now-available/
    https://blogs.msdn.microsoft.com/powershell/2016/02/24/windows-management-framework-wmf-5-0-rtm-packages-has-been-republished/

OS version table https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx

#>

#Get the operating environment
$Arch = (Get-WmiObject -Class Win32_Processor).addresswidth
$OS = (Get-WmiObject Win32_OperatingSystem).version.split('.')[0]
$OSMinor = (Get-WmiObject Win32_OperatingSystem).version.split('.')[1]

#64 bit OS check
if($Arch -eq 64){
    if($OS -eq 6 -and $OSMinor -eq 1){$url = "https://download.microsoft.com/download/2/C/6/2C6E1B4A-EBE5-48A6-B225-2D2058A9CEFB/Win7AndW2K8R2-KB3134760-x64.msu"
                                      $file = "Win7AndW2K8R2-KB3134760-x64.msu"}
    if($OS -eq 6 -and $OSMinor -eq 2){$url = "https://download.microsoft.com/download/2/C/6/2C6E1B4A-EBE5-48A6-B225-2D2058A9CEFB/W2K12-KB3134759-x64.msu"
                                      $file = "W2K12-KB3134759-x64.msu"}
    if($OS -eq 6 -and $OSMinor -eq 3){$url = "https://download.microsoft.com/download/2/C/6/2C6E1B4A-EBE5-48A6-B225-2D2058A9CEFB/Win8.1AndW2K12R2-KB3134758-x64.msu"
                                      $File = "Win8.1AndW2K12R2-KB3134758-x64.msu"}
}
elseif($Arch -eq 32){
    if($OS -eq 6 -and $OSMinor -eq 1){$url = "https://download.microsoft.com/download/2/C/6/2C6E1B4A-EBE5-48A6-B225-2D2058A9CEFB/Win7-KB3134760-x86.msu"
                                      $file = "Win7-KB3134760-x86.msu"}
    if($OS -eq 8 -and $OSMinor -eq 3){$url = "https://download.microsoft.com/download/2/C/6/2C6E1B4A-EBE5-48A6-B225-2D2058A9CEFB/Win8.1-KB3134758-x86.msu"
                                      $file = "Win8.1-KB3134758-x86.msu"}
}

if($url -eq $null -or $file -eq $null){Write-Error "Failed to find valid OS"}
Else{
    $output = Join-Path $env:TEMP $file

    Write-Output "Downloading: $file"
    Write-Output "From: $url"
    Write-Output "To: $output"

    $wc = New-Object System.Net.Webclient
    #May be required in some places with authenticated proxy
    #$wc.UseDefaultCredentials = $true
    #$wc.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f")
    $wc.DownloadFile($url, $output)

    if(test-path $output){ Write-Output "Installing $file"
                           & wusa $output /quiet /norestart}
    else{Write-Warning "File $((Join-Path $output $file)) does not exist"}
}

 

Inventory Large Folders

This is a fairly simple setup using DCM. Say you want to find out where space is being used on machines, either to find user caches of data that isn’t backed up or to help target folders to clean up when drives get full.

Warning: This is a very CPU intensive process. I recommend testing in your environment as execution times will vary greatly on individual configurations.

You will also need to extend DCM execution times so it doesn’t always report as failed. This is optional however as it will still run. See http://blogs.msdn.com/b/fei_xias_blog/archive/2013/10/21/system-center-2012-configmgr-using-vbs-to-extend-the-dcm-script-execution-timeout-value.aspx for instructions. I set mine to 1200 seconds, which is 20 minutes. You could also deploy this as an SCCM package that just runs the powershell file.

First we setup a configuration item

Large Folders 1 Large Folders 2 Large Folders 3 Large Folders 4

And put this in for the script to run:

Inspirations and some of the codebase documented in the comments. This has all been heavily modified for this purpose.

# Get-DirStats.ps1
# Written by Bill Stewart (bstewart@iname.com)
# Outputs file system directory statistics.
# https://gallery.technet.microsoft.com/scriptcenter/Outputs-directory-size-964d07ff
# http://blogs.technet.com/b/heyscriptingguy/archive/2012/05/25/getting-directory-sizes-in-powershell.aspx

#requires -version 2

<#
.SYNOPSIS
Outputs file system directory statistics.

.DESCRIPTION
Outputs file system directory statistics (number of files and the sum of all file sizes) for one or more directories.

.PARAMETER Path
Specifies a path to one or more file system directories. Wildcards are not permitted. The default path is the current directory (.).

.PARAMETER LiteralPath
Specifies a path to one or more file system directories. Unlike Path, the value of LiteralPath is used exactly as it is typed.

.PARAMETER Only
Outputs statistics for a directory but not any of its subdirectories.

.PARAMETER WriteWMI
Checks for and writes to custom WMI namespace

.PARAMETER Every
Outputs statistics for every directory in the specified path instead of only the first level of directories.

.PARAMETER FormatNumbers
Formats numbers in the output object to include thousands separators.

.PARAMETER ShowErrors
Disable error suppression

.PARAMETER Benchmarking
Show benchmarking statistics

.PARAMETER Size
Filter any directories under this amount in MB from being outputted

.PARAMETER Total
Outputs a summary object after all other output that sums all statistics.
#>

[CmdletBinding(DefaultParameterSetName="Path")]
param(
  [parameter(Position=0,Mandatory=$false,ParameterSetName="Path",ValueFromPipeline=$true)]
    [ValidateScript({Test-Path $_ -PathType 'Container'})] 
    $Path=(get-location).Path,
  [parameter(Position=0,Mandatory=$true,ParameterSetName="LiteralPath")]
    [ValidateScript({Test-Path $_ -PathType 'Container'})] 
    [String[]] $LiteralPath,
    [Switch] $Only,
    [Switch] $WriteWMI,
    [Switch] $Every,
    [Switch] $FormatNumbers,
    [Switch] $Total,
  [Switch] $ShowErrors,
  [Switch] $Benchmarking,	
    [ValidateRange(1,90000000)] 
    [Int] $Size
)

begin {
  #We're going to return the seconds it took to run for DCM
  $script:startTime = Get-Date
  if($Benchmarking){
  write-host "Script Started at $script:startTime"
  }

  #Setting the variable here since we are using DCM
  $WriteWMI = $true
  #Silence error output during writeWMI
  If($WriteWMI){$ErrorActionPreference = "silentlycontinue"}
  if ($ShowErrors){$ErrorActionPreference ="Continue"}

  #Create FSO to use later
  $fso = New-Object -comobject Scripting.FileSystemObject
    
  $ParamSetName = $PSCmdlet.ParameterSetName
  if ( $ParamSetName -eq "Path" ) {
    $PipelineInput = ( -not $PSBoundParameters.ContainsKey("Path") ) -and ( -not $Path )
  }
  elseif ( $ParamSetName -eq "LiteralPath" ) {
    $PipelineInput = $false
  }

  if ( $Size -gt 0 ) { $SizeFilter = $true } 
  else { $SizeFilter = $false }

  #Check and setup WMI if we are going to use that
  if ( $WriteWMI ) {
    #Check if there is a size filter, if not set to 2000MB because we only want really big folders to work with
    if ($Size -le 0) { 
  $Size = 2000
  $SizeFilter = $true 
  } 
    
    #Get Local Drive to search
    $script:LocalDrives = get-wmiobject win32_volume | ? { $_.DriveType -eq 3 } | % { get-psdrive $_.DriveLetter[0] } | Select Root

    #Delete existing instances
    $script:Class = "LargeFolders"
    If (Get-WmiObject -List -Namespace "root\cimv2" | Where-Object {$_.Name -eq $script:Class}) {
   Get-WmiObject -Namespace "root\cimv2" -Class $script:Class | Remove-WMIObject
       } 
  
  #Now rebuild it to make sure we have the right fields
  $subClass = New-Object System.Management.ManagementClass ("root\cimv2", [String]::Empty, $null); 
  $subClass["__CLASS"] = $script:Class; 
  $subClass.Qualifiers.Add("Static", $true)
  $subClass.Properties.Add("Path", [System.Management.CimType]::String, $false)
  $subClass.Properties["Path"].Qualifiers.Add("Key", $true)
  $subClass.Properties.Add("FileCount", [System.Management.CimType]::UInt32, $false)
    $subClass.Properties.Add("SubFolderCount", [System.Management.CimType]::UInt32, $false)
  $subClass.Properties.Add("SizeMB", [System.Management.CimType]::UInt32, $false)
    $subClass.Properties.Add("GatherMethod", [System.Management.CimType]::UInt8, $false)
    $subClass.Properties.Add("DateCreated", [System.Management.CimType]::String, $false)
    $subClass.Properties.Add("DateLastModified", [System.Management.CimType]::String, $false)
    $subClass.Properties.Add("SecondsToEnumerate", [System.Management.CimType]::UInt32, $false)
  #This would be WMI CIM_DateTime Native if needed
    #$subClass.Properties.Add("DateCreated", [System.Management.CimType]::DateTime, $false)
    #$subClass.Properties.Add("DateLastModified", [System.Management.CimType]::DateTime, $false)
    $WMIOutput = $subClass.Put()
    }

  # Script-level variables used with -Total.
  [UInt64] $script:totalcount = 0
  [UInt64] $script:totalbytes = 0

  # Returns a [System.IO.DirectoryInfo] object if it exists.
  function Get-Directory {
    param( $item )

    if ( $ParamSetName -eq "Path" ) {
      if ((Test-Path -Path $item -PathType Container) -and ($item.Attributes -notmatch [System.IO.FileAttributes]::ReparsePoint)) {
        $item = Get-Item -Path $item -Force
      }
    }
    elseif ( $ParamSetName -eq "LiteralPath" ) {
      if ( (Test-Path -LiteralPath $item -PathType Container) -and ($item.Attributes -notmatch [System.IO.FileAttributes]::ReparsePoint) ) {
        $item = Get-Item -LiteralPath $item -Force
      }
    }
    if ( $item -and ($item -is [System.IO.DirectoryInfo]) -and ($item.Attributes -notmatch [System.IO.FileAttributes]::ReparsePoint)) {
      return $item
    }
  }

  #Do the benchmark calculations
  function GetElapsedTime([datetime]$starttime) 
  {
    $runtime = [Math]::Round(($(get-date) - $starttime).TotalSeconds)

    If($WriteWMI){
        $retStr = $runtime}
    Else{
        $retStr = [string]::format("{0} sec(s)", $runtime)}
    $retStr
  }
  
  # Filter that outputs the custom object with formatted numbers.
  function Format-Output {
    process {
      $_ | Select-Object Path,
        @{Name="Files"; Expression={"{0:N0}" -f $_.Files}},
        @{Name="Size"; Expression={"{0:N0}" -f $_.Size}}
    }
  }

  # Outputs directory statistics for the specified directory. With -recurse,
  # the function includes files in all subdirectories of the specified
  # directory. With -format, numbers in the output objects are formatted with
  # the Format-Output filter.
  function Get-DirectoryStats {
    param( $directory, $recurse, $format )
    
    #Break out of here if we don't care about this folder
    If ($WriteWMI) 
    {
        #Start counting how long it takes to get data
        $enumerationTime = Get-Date
        #Create Exclude array at runtime to speed comparison so we can exclude items
        #http://blogs.technet.com/b/heyscriptingguy/archive/2011/02/18/speed-up-array-comparisons-in-powershell-with-a-runtime-regex.aspx
        $ExcludedFolders = @("Windows"
                            ,"Users"
                            ,"inetpub"
                            ,"Program Files"
                            ,"Program Files (x86)"
                            ,"ProgramData"
                            ,"Windows\System32"
                            ,"System Volume Information"
                            ,"Windows\Installer"
                            ,"Windows\assembly"
                            ,"Windows\System32\DriverStore"
                            ,"Windows\System32\DriverStore\FileRepository"
                            ,"ProgramData\Package Cache
                            ","ProgramData\Microsoft")
        [regex] $excludeFolders_regex = '(?i)^(' + (($ExcludedFolders | foreach {[regex]::escape($directory.Root.ToString() + $_)}) –join "$|") + ')$'    

        If($directory.FullName -match $excludeFolders_regex) {
        #Write-Warning "Skipping: " $directory.FullName
        Return}
        else {
        #Write-Information $directory.FullName " not in " $excludeFolders_regex
        }
    }

    Write-Progress -Activity "Get-DirStats.ps1" -Status "Reading '$($directory.FullName)'"

    #Use FSO if possible
    #https://gallery.technet.microsoft.com/get-foldersize-224916dd
    $folder = $fso.GetFolder($directory.FullName)

    #test if FSO worked, otherwise go to file by file
    if($folder.size -ge $size*1MB -AND $folder.Type -eq "File Folder") {
      $output = "" | Select Path, Files, SizeMB, GatherMethod
      $output.Path = $folder.Path.ToString()
      $output.Files = $folder.Files.Count
      $output.SizeMB = [Math]::Round($folder.Size/1MB)
      $output.GatherMethod = 1
    }
    elseif($folder.size -eq $null) {
      $files = $directory | Get-ChildItem -Force -Recurse:$recurse | 
       ? {$_.Attributes -notmatch [System.IO.FileAttributes]::ReparsePoint} | 
       Where-Object { -not $_.PSIsContainer }
  
      if ( $files ) {
      Write-Progress -Activity "Get-DirStats.ps1" -Status "Calculating '$($directory.FullName)'"
    
    	If ( $SizeFilter ) 
    {
      $output = $files | Measure-Object -Sum -Property Length | 
      Where-Object {$_.Sum -ge $Size*1MB} |  Select-Object `
  	          @{Name="Path"; Expression={$directory.FullName}},
            @{Name="Files"; Expression={$_.Count; $script:totalcount += $_.Count}},
            @{Name="Size"; Expression={$_.Sum; $script:totalbytes += $_.Sum}},
          @{Name="SizeMB"; Expression={[Math]::Round($_.Sum/1MB)}},
              @{Name="GatherMethod"; Expression={2}}
    }
        elseif ( $SizeFilter = $False ) {
          $output = "" | Select-Object `
          @{Name="Path"; Expression={$directory.FullName}},
          @{Name="Files"; Expression={0}},
          @{Name="Size"; Expression={0}}
        }
   	  Else 
    {
      $output = $files | Measure-Object -Sum -Property Length | Select-Object `
  	          @{Name="Path"; Expression={$directory.FullName}},
            @{Name="Files"; Expression={$_.Count; $script:totalcount += $_.Count}},
            @{Name="Size"; Expression={$_.Sum; $script:totalbytes += $_.Sum}},
      @{Name="SizeMB"; Expression={[Math]::Round($_.Sum/1MB)}}
    }
      }
    } #end elseif $folder.size is null manual query

    #Write data to WMI
    if ($WriteWMI -And $output.SizeMB -ge $Size) { 
    $WMIURL = 'root\cimv2:'+$script:Class
    $PushDataToWMI = ([wmiclass]$WMIURL).CreateInstance()
    $PushDataToWMI.Path =  $output.Path
    $PushDataToWMI.FileCount = $output.Files
        $PushDataToWMI.SubFolderCount = $folder.SubFolders.Count
    $PushDataToWMI.SizeMB = $output.SizeMB
        $PushDataToWMI.GatherMethod = $output.GatherMethod
    $PushDataToWMI.DateCreated = $folder.DateCreated
    $PushDataToWMI.DateLastModified = $folder.DateLastModified
        $elapsed = GetElapsedTime $enumerationTime
        $PushDataToWMI.SecondsToEnumerate = $elapsed
    #WMI CIM_DateTime Namtive if needed
        #$PushDataToWMI.DateCreated = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($folder.DateCreated)
    #$PushDataToWMI.DateLastModified = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($folder.DateLastModified)
    $WmiOutput = $PushDataToWMI.Put()
        #$TotalTime = GetElapsedTime $script:startTime
        #Write-Host $TotalTime ":" $output.Path "with time of" $elapsed "using method" $output.GatherMethod
  }

   if ($WriteWMI=$false){if ( -not $format ) { $output } else { $output | Format-Output }}
  }
}

process {
  # Get the item to process, no matter whether the input comes from the
  # pipeline or not.
  if ( $PipelineInput ) {
    $item = $_
  }
  else {
    if ( $ParamSetName -eq "Path" ) {
      $item = $Path
    }
    elseif ( $ParamSetName -eq "LiteralPath" ) {
      $item = $LiteralPath
    }
  }


  if($WriteWMI){
  foreach($drive in $script:LocalDrives){
    if($Benchmarking -OR $WriteWMI){$iterTime = Get-Date}
    $item = $drive.Root

    # Write an error if the item is not a directory in the file system.
    $directory = Get-Directory -item $item
    if ( -not $directory ) {
      Write-Error -Message "Path '$item' is not a directory in the file system." -Category InvalidType
      return
    }

    # Get the statistics for the first-level directory.
    Get-DirectoryStats -directory $directory -recurse:$false

    # Get the subdirectories of the first-level directory and get the statistics
    # for each of them.
    $directory | Get-ChildItem -Force -Recurse:$true |
      Where-Object { $_.PSIsContainer } | ? {$_.Attributes -notmatch [System.IO.FileAttributes]::ReparsePoint} | 
      ForEach-Object {Get-DirectoryStats -directory $_ -recurse:$true}


    if($Benchmarking -OR $WriteWMI){    
      $elapsed = GetElapsedTime $iterTime ;
        write-warning "   Iteration Time: " $elapsed " for " $item
    }
  }
  }
   else {
    # Write an error if the item is not a directory in the file system.
    $directory = Get-Directory -item $item
    if ( -not $directory ) {
      Write-Error -Message "Path '$item' is not a directory in the file system." -Category InvalidType
      return
    }
  
    # Get the statistics for the first-level directory.
    Get-DirectoryStats -directory $directory -recurse:$false -format:$FormatNumbers
    # -Only means no further processing past the first-level directory.
    if ( $Only ) { return }

    # Get the subdirectories of the first-level directory and get the statistics
    # for each of them.
    $directory | Get-ChildItem -Force -Recurse:$Every |
      Where-Object { $_.PSIsContainer } | ? {$_.Attributes -notmatch [System.IO.FileAttributes]::ReparsePoint} | 
    ForEach-Object {Get-DirectoryStats -directory $_ -recurse:(-not $Every) -format:$FormatNumbers
      }
   }
}

end {
  # If -Total specified, output summary object.
  if ( $Total ) {
    $output = "" | Select-Object `
      @{Name="Path"; Expression={"<Total>"}},
      @{Name="Files"; Expression={$script:totalcount}},
      @{Name="Size"; Expression={$script:totalbytes}}
    if ( -not $FormatNumbers ) { $output } else { $output | Format-Output }
  }
  
  $elapsed = GetElapsedTime $script:startTime;
  if($Benchmarking){
  write-host "Script Ended at $(get-date)"
  write-host "Total Elapsed Time: " $elapsed
  }
  Write-Host $elapsed
}

You can modify any of the variables like size and folders to exclude to meet your needs. I didn’t want to focus on folders I probably could not clean out easily like system folders and the Windows folder will always show over 2GB on machines.

Then setup the configuration baseline to run either on a simple schedule or when it is good for your environment. This is a very processor intensive script that could easily use 50% or more of the CPU for 20+ minutes. I did every saturday here. You could also target a custom collection where machines have hard drive space less than X amount.

Large Folders 5 Large Folders 6 Large Folders 7 Large Folders 8

Then add the WMI entry to hardware inventory. This will need done from a system where this has run already.

Large Folders 10

Then wait for the data and profit

Large Folders 11

 

PowerCLI Profile

This is a cleaned up version of my PowerCLI profile that I use for everything. I can’t confirm everything works as I add and remove items often, but you can test in your environment. I tried documenting everywhere where I found the inspiration. Many things needed adjusted to work correctly and may have broken as I removed personal information.

# Use ". $profile" to reload

Add-PSSnapin Quest.ActiveRoles.ADManagement
Add-PSSnapin Quest.Defender.AdminTools
Import-Module ActiveDirectory
#Import-Module VMWare*
Import-Module BitsTransfer

#Connect to server
connect-viserver -server vdiserver.domain.com


# Properties from http://www.lucd.info/viproperties/
New-VIProperty -Name ToolsVersion -ObjectType VirtualMachine -ValueFromExtensionProperty 'Config.tools.ToolsVersion' -Force 
New-VIProperty -Name ToolsVersionStatus -ObjectType VirtualMachine -ValueFromExtensionProperty 'Guest.ToolsVersionStatus' -Force
New-VIProperty -Name NumberOfVMs -ObjectType Datastore ` -Value {param($ds) $ds.ExtensionData.VM.Length} ` -Force
New-VIProperty -Name FreeGB -ObjectType Datastore ` -Value {param($ds) [Math]::Round($ds.FreeSpaceMb/1KB,1)} ` -Force
New-VIProperty -Name UsedGB -ObjectType Datastore ` -Value {param($ds) [Math]::Round(($ds.CapacityMB-$ds.FreeSpaceMb)/1KB,1)} ` -Force
New-VIProperty -Name PercentFree -ObjectType Datastore -Value {  param($ds)   "{0:P2}" -f ($ds.FreeSpaceMB/$ds.CapacityMB)} -Force
New-VIProperty -Name ProvisionedGB -ObjectType Datastore `  -Value {    param($ds)     [Math]::Round(($ds.ExtensionData.Summary.Capacity - $ds.ExtensionData.Summary.FreeSpace + $ds.ExtensionData.Summary.Uncommitted)/1GB,1)  } `  -BasedONextensionProperty 'Summary' `  -Force
New-VIProperty -Name Datastore -ObjectType Harddisk -Value {  param($hd)   $hd.Filename.Split(']')[0].TrimStart('[')} -Force

function Invoke-SCCMDCMEvaluation
{
    param (
        [Parameter(Mandatory=$true, HelpMessage="Computer Name",ValueFromPipeline=$true)] $ComputerName
           )
$Baselines = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration
# echo $baselines

 $name = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration | Where-Object {$_.DisplayName -match "<baselineName>"} | Select-Object -ExpandProperty Name
 write-host $name 

 $version = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration | Where-Object {$_.DisplayName -match "<baselineName>"} | Select-Object -ExpandProperty Version
 Write-Host $version

 $MC = [WmiClass]"\\$ComputerName\root\ccm\dcm:SMS_DesiredConfiguration"
 Write-Host $MC

 $Method = "TriggerEvaluation"
 $InParams = $mc.psbase.GetMethodParameters($Method)
 $InParams.IsEnforced = $true
 $InParams.IsMachineTarget = $false
 $InParams.Name = "$name"
 $InParams.Version = "$version"
 $inparams.PSBase.properties | select Name,Value | format-Table
 $R = $MC.InvokeMethod($Method, $InParams, $null)
 $R | Format-Table
}

Function DatastoreStatus
{
Get-DataStore | where-object {$_.Name -notlike "*vdi0*"} | Sort DataCenter,Name | Select DataCenter,Name,NumberOfVMs,FreeGB,UsedGB,PercentFree,ProvisionedGB | Format-Table -AutoSize
} #DatastoreStatus


Function KillVMs
{
#Stop-VM [-Kill] [-RunAsync] [-VM] <VirtualMachine[]> 
#From <https://www.vmware.com/support/developer/PowerCLI/PowerCLI41U1/html/Stop-VM.html> 

Clear-Host
Write-Host "`n`n`n`n`n`n`n`n`n"

$VMs = $args | foreach {get-vm $($_)} | Sort Name
$VMs | ForEach-Object {$VMCount++}
$CurrentVMNumber = 0

foreach ($VM in $VMs){
    $CurrentVMNumber++
    Stop-VM -Kill -VM $VM
    $Countdown = 20
   	for($SecondsRemaining = $Countdown; $SecondsRemaining -ge 0; $SecondsRemaining–-)
     {
      Write-Progress -Activity “Powering Off VMs” -PercentComplete (((($VMCount-$CurrentVMNumber)*20+$SecondsRemaining)/($VMCount*20))*100) -Status “Next machine poweroff in $SecondsRemaining seconds, $($VMCount-$CurrentVMNumber) machines remaining” -secondsRemaining (($VMCount-$CurrentVMNumber)*20+$SecondsRemaining);Sleep -Seconds 1;
     }
    Write-Progress -Activity “Killing” -completed
  }

VMInfo $args
}


Function VMsNotMigrated
{

$VMs = Get-Datacenter | Get-Datastore | where-object {$_.Name -notlike "*vdi*"} | Foreach-Object {
    $ds = $_.Name
    $_ | Get-VM | Sort VMHost,PowerState,Name | Select Name, PowerState, VMHost, @{N="DataStore";E={@((Get-Harddisk $_).Datastore)}} }

$VMs | Format-Table

Write-Host "`n`nMachines left to migrate: $($VMs.Count)`n`n`n"


} #VMsNotMigrated



Function MoveVMsNotMigrated
{

$allvms = @()

#$vms = Get-Vm | Where {$_.Guest.OSFullName -match "Windows"}

$vms = Get-Datacenter | Get-Datastore | where-object {$_.Name -notlike "*vdi*"} | Foreach-Object {
    $ds = $_.Name
    $_ | Get-VM }

$countvm = 1

foreach($vm in $vms){
  $vmstat = "" | Select VmName, CPU, Datastore
  $vmstat.VmName = $vm.name
  $vmview = get-vm $vm.name | Get-View
  $vmstat.CPU = $vmview.Summary.QuickStats.OverallCpuUsage
  $vmstat.Datastore = $((Get-Harddisk $vm.name).Datastore)
  $allvms += $vmstat
  Write-Host "Gathering data for $vm with CPU usage at $($vmstat.CPU) MHz ($countvm of $($vms.count))"
  $countvm++
  } 

$VMs = $allvms | Sort CPU | Select VmName, CPU, Datastore

#seed with first datastore
$datastore = "VDI_001"
$countvm = 1

foreach($vm in $VMs)
 {
  Write-Host "Moving VM $countvm of $($VMs.Count) named $($vm.VmName) to $datastore from $($vm.Datastore)"
  Write-Host "Logged on users = $((Get-WMILoggedOnUser -ComputerName $vm.VmName -Logontype 2,10).Count)"
  
  #This should allow 4 to move at all times
  If((Get-Task -Status Running).Count -ge 4){Write-Host "Waiting for running tasks to complete. Currently $((Get-Task -Status Running).Count) running"}

  While((Get-Task -Status Running).Count -ge 4)
  {Write-Host -NoNewline "$((Get-Task -Status Running).Count)..."
  Start-Sleep 60}

  #Break the count and indent
  Write-Host ""
  Write-Host -NoNewLine "    "

  If($datastore -eq "VDI_001")
  {
  #If VDI_001 then start move and contine script
  Move-VM -Datastore $datastore -RunAsync -VM $vm.VmName
  $datastore = "VDI_002"}
  Else
  {
  #If VDI_002 then start move and wait to complete
  Move-VM -Datastore $datastore -RunAsync -VM $vm.VmName
  $datastore = "VDI_001"}
  $countvm++
 }

} #MoveVMsNotMigrated


## Get-RemoteRegistry
## From http://poshcode.org/615
########################################################################################
## Version: 2.1
##  + Fixed a pasting bug 
##  + I added the "Properties" parameter so you can select specific registry values
## NOTE: you have to have access, and the remote registry service has to be running
########################################################################################
## USAGE:
##   Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP"
##     * Returns a list of subkeys (because this key has no properties)
##   Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727"
##     * Returns a list of subkeys and all the other "properties" of the key
##   Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\Version"
##     * Returns JUST the full version of the .Net SP2 as a STRING (to preserve prior behavior)
##   Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" Version
##     * Returns a custom object with the property "Version" = "2.0.50727.3053" (your version)
##   Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" Version,SP
##     * Returns a custom object with "Version" and "SP" (Service Pack) properties
##
##  For fun, get all .Net Framework versions (2.0 and greater) 
##  and return version + service pack with this one command line:
##
##    Get-RemoteRegistry $RemotePC "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP" | 
##    Select -Expand Subkeys | ForEach-Object { 
##      Get-RemoteRegistry $RemotePC "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\$_" Version,SP 
##    }
##
########################################################################################
# 

Function Get-RemoteRegistry {

param(
    [string]$computer = $(Read-Host "Remote Computer Name")
   ,[string]$Path     = $(Read-Host "Remote Registry Path (must start with HKLM,HKCU,etc)")
   ,[string[]]$Properties
   ,[switch]$Verbose
)

if ($Verbose) { $VerbosePreference = 2 } # Only affects this script.

   $root, $last = $Path.Split("\")
   $last = $last[-1]
   $Path = $Path.Substring($root.Length + 1,$Path.Length - ( $last.Length + $root.Length + 2))
   $root = $root.TrimEnd(":")

   #split the path to get a list of subkeys that we will need to access
   # ClassesRoot, CurrentUser, LocalMachine, Users, PerformanceData, CurrentConfig, DynData
   switch($root) {
      "HKCR"  { $root = "ClassesRoot"}
      "HKCU"  { $root = "CurrentUser" }
      "HKLM"  { $root = "LocalMachine" }
      "HKU"   { $root = "Users" }
      "HKPD"  { $root = "PerformanceData"}
      "HKCC"  { $root = "CurrentConfig"}
      "HKDD"  { $root = "DynData"}
      default { return "Path argument is not valid" }
   }

Write-Output $computer

   #Access Remote Registry Key using the static OpenRemoteBaseKey method.
   Write-Verbose "Accessing $root from $computer"
   $rootkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($root,$computer)
   if(-not $rootkey) { Write-Error "Can't open the remote $root registry hive" }

   Write-Verbose "Opening $Path"
   $key = $rootkey.OpenSubKey( $Path )
   if(-not $key) { Write-Error "Can't open $($root + '\' + $Path) on $computer" }

   $subkey = $key.OpenSubKey( $last )
   
   $output = new-object object

   if($subkey -and $Properties -and $Properties.Count) {
      foreach($property in $Properties) {
         Add-Member -InputObject $output -Type NoteProperty -Name $property -Value $subkey.GetValue($property)
      }
      Write-Output $output
   } elseif($subkey) {
      Add-Member -InputObject $output -Type NoteProperty -Name "Subkeys" -Value @($subkey.GetSubKeyNames())
      foreach($property in $subkey.GetValueNames()) {
         if($subkey.GetValue($property) -like "*.otf"){Add-Member -InputObject $output -Type NoteProperty -Name $property -Value $subkey.GetValue($property)}
      }
      Write-Output $output
   }
   else
   {
      $key.GetValue($last)
   }
 }




Function FindOpenTypeFonts
{
#$args | ForEach {$($_)} | Out-File -Append c:\temp\Fonts.log
$args | ForEach {Get-RemoteRegistry $($_) "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Fonts\"} | Out-File -Append c:\temp\Fonts.log
}






Function GetAppVMembers
{
$groups = Get-ADGroup -Filter {Name -like "AENTVS*"} | Select Name
 foreach ($group in $groups){
    $members = Get-ADGroupMember -identity $group.Name | select name,SamAccountName 
  foreach ($member in $members)
    {$output = $output + $group.name + "," + $member.name + "," + $member.SamAccountName + "`n"} 
}
$output | out-file C:\Temp\Groupmembers.csv
}

Function PowerUpVMs
{

Clear-Host
Write-Host "`n`n`n`n`n`n`n`n`n"

$VMs = $args | foreach {get-vm $($_)} | Sort Name
$VMs | ForEach-Object {$VMCount++}
$CurrentVMNumber = 0

foreach ($VM in $VMs){
    $CurrentVMNumber++
    Start-VM -VM $VM
    $Countdown = 600
   	for($SecondsRemaining = $Countdown; $SecondsRemaining -ge 0; $SecondsRemaining–-)
     {
      Write-Progress -Activity “Powering On VMs” -PercentComplete (((($VMCount-$CurrentVMNumber)*600+$SecondsRemaining)/($VMCount*600))*100) -Status “Next machine powerup in $SecondsRemaining seconds, $($VMCount-$CurrentVMNumber) machines remaining” -secondsRemaining (($VMCount-$CurrentVMNumber)*600+$SecondsRemaining);Sleep -Seconds 1;
     }
    Write-Progress -Activity “Building” -completed
  }

VMInfo $args

}

Function PowerUpVMsQuick
{

Clear-Host
Write-Host "`n`n`n`n`n`n`n`n`n"

$VMs = $args | foreach {get-vm $($_)} | Sort Name
$VMs | ForEach-Object {$VMCount++}
$CurrentVMNumber = 0

foreach ($VM in $VMs){
    $CurrentVMNumber++
    Start-VM -VM $VM
    $Countdown = 300
   	for($SecondsRemaining = $Countdown; $SecondsRemaining -ge 0; $SecondsRemaining–-)
     {
      Write-Progress -Activity “Powering On VMs” -PercentComplete (((($VMCount-$CurrentVMNumber)*300+$SecondsRemaining)/($VMCount*300))*100) -Status “Next machine powerup in $SecondsRemaining seconds, $($VMCount-$CurrentVMNumber) machines remaining” -secondsRemaining (($VMCount-$CurrentVMNumber)*300+$SecondsRemaining);Sleep -Seconds 1;
     }
    Write-Progress -Activity “Building” -completed
  }

VMInfo $args

} #End powerupvmsquick

Function PowerUpVMsVeryQuick
{

Clear-Host
Write-Host "`n`n`n`n`n`n`n`n`n"

$VMs = $args | foreach {get-vm $($_)} | Sort Name
$VMs | ForEach-Object {$VMCount++}
$CurrentVMNumber = 0

foreach ($VM in $VMs){
    $CurrentVMNumber++
    Start-VM -VM $VM
    $Countdown = 20
   	for($SecondsRemaining = $Countdown; $SecondsRemaining -ge 0; $SecondsRemaining–-)
     {
      Write-Progress -Activity “Powering On VMs” -PercentComplete (((($VMCount-$CurrentVMNumber)*20+$SecondsRemaining)/($VMCount*20))*100) -Status “Next machine powerup in $SecondsRemaining seconds, $($VMCount-$CurrentVMNumber) machines remaining” -secondsRemaining (($VMCount-$CurrentVMNumber)*20+$SecondsRemaining);Sleep -Seconds 1;
     }
    Write-Progress -Activity “Building” -completed
  }

VMInfo $args


} #End powerupvmsveryquick


function AcknowledgeAlarms
{

$alarmMgr = Get-View AlarmManager

Get-VM | where {$_.ExtensionData.TriggeredAlarmState} | %{
  $vm = $_ 
  $vm.ExtensionData.TriggeredAlarmState | %{
        $alarmMgr.AcknowledgeAlarm($_.Alarm,$vm.ExtensionData.MoRef)
    }
}

} # End AcknowledgeAlarms


function ExportNetworkAdapters
{
# Get the Virtual Network Adapter  

$VMs = Get-VM | where-object {$_.Folder -match "AM US PAM"} | Sort Name
$Data = @()  
foreach ($VM in $VMs){  
$NICs = $VM.NetworkAdapters  
foreach ($NIC in $NICs) {  
$into = New-Object PSObject  
Add-Member -InputObject $into -MemberType NoteProperty -Name VMname $VM.Name  
Add-Member -InputObject $into -MemberType NoteProperty -Name NICtype $NIC.Type 
$Data += $into 
}  
}  

$Data | Export-Csv -Path c:\temp\VM_NICs.csv -NoTypeInformation 
} # End ExportNetworkAdapters


function GetVMHostCapacity
{
  $VMHosts = Get-VMHost |Sort Name | Get-View
  $VMServers = @()

  ForEach ($VMHost in $VMHosts)
  {
    Write-Host "Getting Information for $($VMHost.Name)"
    $VMServer = [PSCustomObject]@{		
      Name = $VMHost.Name
      Type = $VMHost.Hardware.SystemInfo.Vendor+ “ “ + $VMHost.Hardware.SystemInfo.Model
      CPU = 	$VMHost.Hardware.CpuPkg[0].Description
      CPUPackages = $VMHost.Hardware.CpuInfo.NumCpuPackages
      CPUCores = $VMHost.Hardware.CpuInfo.NumCpuCores
      CPUMhz = [math]::round($VMHost.Hardware.CpuInfo.Hz / 1000000, 0)
      TotalHostMhz = 0
      MemGB = [math]::round($VMHost.Hardware.MemorySize / 1GB, 0)
      NumVMs = (Get-View -ViewType "VirtualMachine" -Property Name -Filter @{"Runtime.PowerState"="PoweredOn"} -SearchRoot $(Get-View -ViewType "HostSystem" -Filter @{"Name"="$($VMHost.Name)"} -Property Name).MoRef | Measure-Object).Count
      GuestCPUs = (Get-VMHost $($VMHost.Name) | Get-VM | Where-Object {$_.PowerState -eq "PoweredOn"} | Measure-Object "NumCpu" -Sum).Sum
      GuestMem = [math]::round((Get-VMHost $($VMHost.Name) | Get-VM | Where-Object {$_.PowerState -eq "PoweredOn"} | Measure-Object "MemoryGB" -Sum).Sum,0)
      MhzPerVM = 0
      MhzPervCPU = 0
      GBRAMPerVM = 0
    }
    $VMServer.TotalHostMhz = $VMServer.CPUPackages * $VMServer.CPUCores * $VMServer.CPUMhz
    $VMServer.MhzPerVM   = [math]::round($VMServer.TotalHostMhz / $VMServer.NumVMs,0)
    $VMServer.MhzPervCPU = [math]::round($VMServer.TotalHostMhz / $VMServer.GuestCPUs,0)
    $VMServer.GBRAMPerVM = [math]::round($VMServer.MemGB/$VMServer.NumVMs,0)
      
    $VMServers += $VMServer
  }
  $VMServers | ft -auto
  $VMServers | Export-CSV -NoTypeInformation c:\temp\vm_capacity.csv
} #End GetVMHostCapacity


function ExportAllVMInformation
{


#Variables
$Date = get-date
$Datefile = ( get-date ).ToString(‘yyyy-MM-dd-hhmmss’)
$ErrorActionPreference = "SilentlyContinue"
# Variable to change
$CreateCSV= "yes"
$GridView = "no"
$HTML = "yes"
$DisplayHTMLOnScreen = "yes"
$EmailHTML = "yes"
$SendEmail = "yes"
$EmailFrom = "email@address.com"
$EmailTo = "email@address.com"
$EmailSubject = "VMs settings information"
$EmailSMTP = "mail.domain.com"
$FileHTML = New-Item -type file "C:\temp\VMInfo_$datefile.html"
$FileCSV = New-Item -type file "C:\temp\VMInfo_$datefile.csv"
 
#Add Text to the HTML file 
Function Create-HTMLTable
{
param([array]$Array)
$arrHTML = $Array | ConvertTo-Html
$arrHTML[-1] = $arrHTML[-1].ToString().Replace(‘</body></html>’,"")
Return $arrHTML[5..2000]
}
 
$output = @()
$output += ‘<html><head></head><body>’
$output += 



‘<style>table{border-style:solid;border-width:1px;font-size:8pt;background-color:#ccc;width:100%;}th{text-align:left;}td{background-color:#fff;width:20%;border-style:so 
lid;border-width:1px;}body{font-family:verdana;font-size:12pt;}h1{font-size:12pt;}h2{font-size:10pt;}</style>’
$output += ‘<H1>VMware VM information</H1>’
$output += ‘<H2>Date and time</H2>’,$date 

#Gathering VM settings
Write-Host "Gathering VM statistics"
$Report = @()
Get-VM | Sort Name -Descending | %{
  
  $vm = Get-View $_.ID
    $vms = "" | Select-Object VMName, Folder, Hostname, IPAddress, OS, Boottime, VMState, TotalCPU, CPUAffinity, CPUHotAdd, CPUShare, CPUlimit, OverallCpuUsage, CPUreservation, TotalMemory, MemoryShare, MemoryUsage, MemoryHotAdd, MemoryLimit, MemoryReservation, Swapped, Ballooned, Compressed, TotalNics, ToolsStatus, ToolsVersion, HardwareVersion, TimeSync, CBT
    $vms.VMName = $vm.Name
    $vms.VMFolder = $vm.Folder
    $vms.Hostname = $vm.guest.hostname
$vms.IPAddress = $vm.guest.ipAddress
$vms.OS = $vm.Config.GuestFullName
$vms.Boottime = $vm.Runtime.BootTime
$vms.VMState = $vm.summary.runtime.powerState
    $vms.TotalCPU = $vm.summary.config.numcpu
    $vms.CPUAffinity = $vm.Config.CpuAffinity
$vms.CPUHotAdd = $vm.Config.CpuHotAddEnabled
$vms.CPUShare = $vm.Config.CpuAllocation.Shares.Level
$vms.TotalMemory = $vm.summary.config.memorysizemb
    $vms.MemoryHotAdd = $vm.Config.MemoryHotAddEnabled
$vms.MemoryShare = $vm.Config.MemoryAllocation.Shares.Level
$vms.TotalNics = $vm.summary.config.numEthernetCards
$vms.OverallCpuUsage = $vm.summary.quickStats.OverallCpuUsage
    $vms.MemoryUsage = $vm.summary.quickStats.guestMemoryUsage
    $vms.ToolsStatus = $vm.guest.toolsstatus
    $vms.ToolsVersion = $vm.config.tools.toolsversion
$vms.TimeSync = $vm.Config.Tools.SyncTimeWithHost
$vms.HardwareVersion = $vm.config.Version
    $vms.MemoryLimit = $vm.resourceconfig.memoryallocation.limit
    $vms.MemoryReservation = $vm.resourceconfig.memoryallocation.reservation
    $vms.CPUreservation = $vm.resourceconfig.cpuallocation.reservation
    $vms.CPUlimit = $vm.resourceconfig.cpuallocation.limit
$vms.CBT = $vm.Config.ChangeTrackingEnabled
$vms.Swapped = $vm.Summary.QuickStats.SwappedMemory
$vms.Ballooned = $vm.Summary.QuickStats.BalloonedMemory
$vms.Compressed = $vm.Summary.QuickStats.CompressedMemory
$Report += $vms
}
 
#Output
if ($GridView -eq "yes") {
$report | Out-GridView }
 
if ($CreateCSV -eq "yes") {
$report | Export-Csv $FileCSV -NoTypeInformation }
 
if ($HTML -eq "yes") {
$output += ‘<p>’
$output += ‘<H2>VMware VM information</H2>’
$output += ‘<p>’
$output += Create-HTMLTable 


$report
$output += ‘</p>’
$output += ‘</body></html>’ 
$output | Out-File $FileHTML }
 
if ($DisplayHTMLOnScreen -eq "yes") {
ii $FileHTML}
 
if ($SendEmail -eq "yes") {
Send-MailMessage –From $EmailFrom –To $EmailTo –Subject $EmailSubject –SmtpServer $EmailSMTP -Attachments $FileHTML } 


} #End ExportAllVMInformation




Function Get-HostMemory 
{

param([switch]$refresh,$hostname=”*”)

$vmhosts=Get-vmhost $hostname

Function get-memory{
 $output=@()
 ForEach($vmhost in $vmhosts)
 {
 $stat=get-stat -entity $vmhost -stat mem* -realtime
 $Objects = New-Object PSObject -Property ([ordered]@{
 Name=$vmhost.name
 TotalGB=”{0:N2}” -f ($vmhost.memorytotalGB)
 GrantedGB=$granted=”{0:N2}” -f (($stat |Where-Object{$_.metricid -like  “mem.granted.average”}|select -expand value -first 1)/1MB)
 ConsumedGB=”{0:N2}” -f (($stat |Where-Object{$_.metricid -like “mem.consumed.average”}|select -expand value -first 1)/1MB)
 ActiveGB=$active=”{0:N4}” -f (($stat |Where-Object{$_.metricid -like “mem.active.average”}|select -expand value -first 1)/1MB)
 Usage =”{0:P0}” -f (($stat |Where-Object{$_.metricid -like “mem.usage.average”} |select -expan value -first 1)/100)
 })
 $objects|add-member –MemberType NoteProperty -Name UsageActive -value (“{0:P0}” -f ($active/$granted))
 $output+= $objects
 }
 $output | Format-table -auto
 }

IF ($refresh)
 {
 while(0 -ne 1)
 {get-memory
 sleep 15}
 }
 Else {Get-memory}

} # End Get-HostMemory 




#These all use this class http://msdn.microsoft.com/en-us/library/system.security.cryptography.aspx
#Why we still do MD5 hash in Config Manager as the SHA1 hash has not been used for content security since SMS 2003 RTM?

#We use this value as a quick integrity check as it's very fast. Something to note is that the older MD5 Hashes are always 32 characters in length and corresponds to the "Hash" column in the SQL Database. Newer (as of SMS 2003 SP1 and Config Mgr) and more secure SHA1 hashes are always 40 characters in length and correspond to the "New Hash" column in the SQL DB.

function Get-SHAFolderHash ($folder) 
{
 dir $folder -Recurse | ?{!$_.psiscontainer} | %{[Byte[]]$contents += [System.IO.File]::ReadAllBytes($_.fullname)}
 $hasher = [System.Security.Cryptography.SHA1]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
}

function Get-MD5FolderHash ($folder)
{
 dir $folder -Recurse | ?{!$_.psiscontainer} | %{[Byte[]]$contents += [System.IO.File]::ReadAllBytes($_.fullname)}
 $hasher = [System.Security.Cryptography.MD5CryptoServiceProvider]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
}

function Get-AllFolderHash ($folder)
{
 #dir $folder -Recurse | ?{!$_.psiscontainer} | %{[Byte[]]$contents += [System.IO.File]::ReadAllBytes($_.fullname)}

dir $folder -Recurse | %{[Byte[]]$contents += [System.IO.File]::ReadAllBytes($_.fullname)}

Write-Host "KeyedHashAlgorithm"
 $hasher = [System.Security.Cryptography.KeyedHashAlgorithm]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "MD5"
 $hasher = [System.Security.Cryptography.MD5]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "MD5Cng"
 $hasher = [System.Security.Cryptography.MD5Cng]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "HMACMD5"
 $hasher = [System.Security.Cryptography.HMACMD5]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "RIPEMD160"
 $hasher = [System.Security.Cryptography.RIPEMD160]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "SHA1Managed"
 $hasher = [System.Security.Cryptography.SHA1Managed]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "SHA1"
 $hasher = [System.Security.Cryptography.SHA1]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "SHA256"
 $hasher = [System.Security.Cryptography.SHA256]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "SHA384"
 $hasher = [System.Security.Cryptography.SHA384]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "SHA512"
 $hasher = [System.Security.Cryptography.SHA512]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "HMACRIPEMD160"
 $hasher = [System.Security.Cryptography.HMACRIPEMD160]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "HMACSHA1"
 $hasher = [System.Security.Cryptography.HMACSHA1]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "HMACSHA256"
 $hasher = [System.Security.Cryptography.HMACSHA256]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "HMACSHA384"
 $hasher = [System.Security.Cryptography.HMACSHA384]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))
Write-Host "HMACSHA512"
 $hasher = [System.Security.Cryptography.HMACSHA512]::Create()
 [string]::Join("",$($hasher.ComputeHash($contents) | %{"{0:x2}" -f $_}))



}

Function GetVMFolderSizes
{
$searchSpec1 = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchSpec1.details = New-Object VMware.Vim.FileQueryFlags
$searchSpec1.details.fileSize = $true
$searchSpec1.query += New-Object VMware.Vim.FolderFileQuery

$searchSpec2 = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchSpec2.details = New-Object VMware.Vim.FileQueryFlags
$searchSpec2.details.fileSize = $true
$searchSpec2.query += New-Object VMware.Vim.FileQuery

$report = @()

Get-Datastore | Get-View | % {
  $browser = Get-View -Id ($_.Browser)

  $dsPath = "[" + $_.Summary.Name + "] /"
  
  $folderList = $browser.SearchDatastore($dsPath, $searchSpec1)

  foreach($folder in $folderList.File){

    $folderPath = $dsPath + $folder.Path

    $fldresult = $browser.SearchDatastore($folderPath, $searchSpec2)
    $fldSize = 0
    foreach($file in $fldresult.File){
      $fldSize += $file.FileSize
    }
    $row = "" | select DSName, FolderName, FolderSize
    $row.DSName = $folderList.FolderPath
    $row.FolderName = $folder.Path
    $row.FolderSize = $fldSize
    $report += $row
  }
}

$report | Export-Csv "c:\Temp\VMFoldersizes.csv" -noTypeInformation
Write-Host "Wrote output to c:\temp\VMFoldersizes.csv"

}

Function GetVMBuildInfo
{

$VM = $args[0]
Get-VIEvent -maxsamples 100000 -Start (Get-Date).AddDays(-30) | where {($_.Gettype().Name-eq "VmCreatedEvent" -or $_.Gettype().Name-eq "VmBeingClonedEvent" -or $_.Gettype().Name-eq "VmBeingDeployedEvent") -and ($_.FullFormattedMessage -match $($VM)) } |Sort CreatedTime -Descending |Select CreatedTime, UserName,FullformattedMessage | ft -Autosize

}


Function UpdateVMInformation
{
connect-viserver -server vdiserver.domain.com
get-vm | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | Export-CSV -NoTypeInformation -Append c:\temp\all_VMS.csv
Get-ADComputer -Filter "Name -like '*'" -SearchBase "DC=test,DC=test,DC=com" -Server "test" |  Select Name, DistinguishedName, DNSHostName, Enabled | Export-CSV -NoTypeInformation -append c:\temp\AD_VMs.csv
}

function VMInfo
{

$args | foreach {get-vm $($_)} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

}



function VMInfoDetails
{

$args | foreach {get-vm $($_)} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}},@{N="Host";E={([string]($_.Host)).Split("\.")[0]}},@{N="DataStore";E={@(Get-HardDisk -VM $($_.Name))[0].Filename.Split(" ")[0]}},PowerState | ft -Autosize

}

function ExportVMInfoDetails
{

Get-VM | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}},@{N="Host";E={([string]($_.Host)).Split("\.")[0]}},@{N="DataStore";E={@(Get-HardDisk -VM $($_.Name))[0].Filename.Split(" ")[0]}},PowerState,NumCpu,MemoryGB | Export-CSV -NoTypeInformation c:\temp\VMInfoDetails.csv

}


function VMInfoExportSQL
{

get-vm | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}},@{N="Host";E={([string]($_.Host)).Split("\.")[0]}},@{N="DataStore";E={@(Get-HardDisk -VM $($_.Name))[0].Filename.Split(" ")[0]}},PowerState | ft -Autosize

}



function ExportVMInfo
{

$args | foreach {get-vm $($_)} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | Export-CSV -NoTypeInformation c:\temp\VMInfo.csv

}

function ExtendTo100GB
{

$args | foreach {Get-HardDisk -VM $($_)} | Sort Name | Set-HardDisk -CapacityGB 100 -Confirm:$false

}

function GetVHDInfo
{

$args | foreach {Get-HardDisk -VM $($_)} | Sort Name

}

function VMBuildInfo
{

$args | foreach {get-vm $($_)} | Sort Name | select Name,Folder,NumCpu,MemoryGB,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

}

function VMFolderBuildInfo
{

get-vm | where-object {$_.Folder -match $($args[0])} | Sort Name | select Name,Folder,NumCpu,MemoryGB,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

}

function ReleaseIPAndPowerOffVMs
{

#Setup credentials
$VMAdminCreds = New-Object System.Management.Automation.PsCredential("username", (ConvertTo-SecureString "password" -AsPlainText -Force))

#Setup VM List
Write-Host "Getting list of VMs to use"
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

#List VMS
$VMs | foreach {get-vm $_.Name} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

ForEach ($vm in $VMs)
{

$vmname = $vm.Name

Write-Host "Release IP on $vmname"
$script = 'c:\Windows\System32\ipconfig.exe /release'
Invoke-VMScript -ToolsWaitSecs 240 -ScriptText $script -VM $vmname -GuestCredential $VMAdminCreds -RunAsync:$true -ScriptType Bat

If ($vm.OSName -match "Microsoft Windows 7")
{
$script = 'c:\Windows\System32\ipconfig.exe /release6'
Invoke-VMScript -ToolsWaitSecs 240 -ScriptText $script -VM $vmname -GuestCredential $VMAdminCreds -RunAsync:$true -ScriptType Bat
}

#Shutdown VM
Shutdown-VMGuest -VM $vmname -Confirm:$false

}

}


function ApplyCustomization
{

#setup customization scripts
$Win7spec = Get-OSCustomizationSpec -name 'WIN7'
$WinXPspec = Get-OSCustomizationSpec -name 'VDIXP'

#Get VMs
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{
  $VM_NAME = Get-VM -Name $vm.Name

#	If ($vm.OSName -match "Microsoft Windows 7"){
    $VM_NAME | set-vm -OSCustomizationSpec $Win7spec -Confirm:$false
#		}
#	If ($vm.OSName -match "Microsoft Windows XP Professional"){
#		$VM_NAME | set-vm -OSCustomizationSpec $WinXPspec -Confirm:$false
#		}
}
} #end ApplyCustomization


function SetVMTo4GBRAM
{

#Get VMs
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{
  $VM_NAME = Get-VM -Name $vm.Name
  $VM_NAME | set-vm -MemoryGB 4 -Confirm:$false
}

} #end SetVMTo4GBRAM

function SetVMTo3GBRAM
{

#Get VMs
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{
  $VM_NAME = Get-VM -Name $vm.Name
  $VM_NAME | set-vm -MemoryGB 3 -Confirm:$false
}

} #end SetVMTo4GBRAM


function SetVMTo2Processors
{

#Get VMs
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{
  $VM_NAME = Get-VM -Name $vm.Name
  $VM_NAME | set-vm -NumCpu 2 -Confirm:$false
}

} #end SetVMTo2Processors


function RebootVMs
{
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{
$vmname = $vm.Name
Restart-VMGuest -VM $vmname
}
}

function UpdateVMTools
{

#$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

#$VMs = Get-Cluster "CR VDI Cluster 01" | get-vm | where {$_.powerstate -ne "PoweredOff" } | where {$_.name -like "V01PRD*"} | where {$_.Guest.ToolsVersionStatus -ne "guestToolsCurrent"} | % { get-view $_.id } | select Name, @{ Name="ToolsVersion"; Expression={$_.config.tools.toolsVersion}}, @{ Name="ToolStatus"; Expression={$_.Guest.ToolsVersionStatus}}

$VMs = Get-Cluster "CR VDI Cluster 01" | get-vm | where {$_.powerstate -ne "PoweredOff" } | % { get-view $_.id } | where {$_.Guest.ToolsStatus -eq "toolsOld" -or $_.Guest.ToolsStatus -eq "toolsNotInstalled" -or $_.Guest.ToolsStatus -eq "toolsNotRunning"} | select Name

#, @{ Name="ToolsVersion"; Expression={$_.config.tools.toolsVersion}}, @{ Name="ToolStatus"; Expression={$_.Guest.ToolsVersionStatus}}, @{ Name="ToolState"; Expression={$_.Guest.ToolsStatus}}

Clear-Host
Write-Host "`n`n`n`n`n`n`n`n`n"

$VMs | ForEach-Object {$VMCount++}
$CurrentVMNumber = 0

foreach ($VM in $VMs){
    $CurrentVMNumber++
    $vmname = $vm.Name
    Write-Host "$CurrentVMNumber of $VMCount : Updating tools on $vmname"
    Update-Tools -VM $vmname -NoReboot:$True
    $Countdown = 10
   	for($SecondsRemaining = $Countdown; $SecondsRemaining -ge 0; $SecondsRemaining–-)
     {
      Write-Progress -Activity “Updating VM tools” -PercentComplete (((($VMCount-$CurrentVMNumber)*10+$SecondsRemaining)/($VMCount*10))*100) -Status “Next machine update in $SecondsRemaining seconds, $($VMCount-$CurrentVMNumber) machines remaining” -secondsRemaining (($VMCount-$CurrentVMNumber)*10+$SecondsRemaining);Sleep -Seconds 1;
     }
    Write-Progress -Activity “Update” -completed
  }

Get-Cluster "CR VDI Cluster 01" | get-vm | where {$_.powerstate -ne "PoweredOff" } | % { get-view $_.id } | where {$_.Guest.ToolsStatus -eq "toolsOld"} | select Name, @{ Name="ToolsVersion"; Expression={$_.config.tools.toolsVersion}}, @{Name="ToolStatus"; Expression={$_.Guest.ToolsVersionStatus}}, @{ Name="ToolState"; Expression={$_.Guest.ToolsStatus}} | Sort Name

}



function DeleteVMs
{

$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{

$vmname = $vm.Name
#get-vm $vmname | where {$_.powerstate -ne "PoweredOff" } | Shutdown-VMGuest -VM $_.Name -Confirm:$false
Remove-VM $vmname -DeletePermanently -Confirm:$false

}

} #End DeleteVMs


function PowerOffVMs
{

$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{

$vmname = $vm.Name
#Shutdown VM
Shutdown-VMGuest -VM $vmname -Confirm:$false

}
} #End PoweroffVMs

function PowerOnVMs
{

$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{

$vmname = $vm.Name
#Start VM
Start-VM -VM $vmname -Confirm:$false

}
} #End poweronVMS


function RebootVMs
{

$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

ForEach ($vm in $VMs)
{

$vmname = $vm.Name
#Start VM
Restart-VM -VM $vmname -Confirm:$false

}
} #End RebootVMs


function MoveVMToProduction
{

#Setup credentials
$VMAdminCreds = New-Object System.Management.Automation.PsCredential("username", (ConvertTo-SecureString "password" -AsPlainText -Force))

#Setup VM List
Write-Host "Getting list of VMs to use"
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

#List VMS
$VMs | foreach {get-vm $_.Name} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

ForEach ($vm in $VMs)
{

$vmname = $vm.Name

Write-Host "Working with the VM: $vmname"

Write-Host "Change Network if needed"
$ProdNetworkSwitch = "vm12345"
$DRNetworkSwitch = "vm23456"
Get-NetworkAdapter -VM $vmname | Where {$_.NetworkName -eq $DRNetworkSwitch } | Set-NetworkAdapter -NetworkName $ProdNetworkSwitch -Confirm:$false

Start-Sleep -s 10

#Start Computer
Start-VM -VM $vmname -Confirm:$false

}

Write-Host "Sleep 300 seconds while last machine powers up"
Start-Sleep -s 300

#List VMs Again with new IPs
$VMs | foreach {get-vm $_.Name} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

} #End MoveVMToProduction


# May need to implement Set-VirtualPortGroup
# https://www.vmware.com/support/developer/PowerCLI/PowerCLI41U1/html/Set-VirtualPortGroup.html

function MoveVMToSecondNetwork
{


#Setup VM List
Write-Host "Getting list of VMs to use"
$VMs = $args | foreach {get-vm $($_)} | Sort Name | select Name, @{N="OSName";E={$_.guest.OSFullName}}

#List VMS
$VMs | foreach {get-vm $_.Name} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

#switch network
ForEach ($vm in $VMs)
{

$vmname = $vm.Name

$ProdNetworkSwitch = "vm12345"
$SecondsNetworkSwitch = "vm23456"
Get-NetworkAdapter -VM $vmname | Where {$_.NetworkName -eq $ProdNetworkSwitch } | Set-NetworkAdapter -NetworkName $SecondsNetworkSwitch -Confirm:$false
Start-Sleep -s 10
Start-VM -VM $vmname -Confirm:$false

}

Write-Host "Sleep 300 seconds while last machine powers up"
Start-Sleep -s 300

#List VMs Again with new IPs
$VMs | foreach {get-vm $_.Name} | Sort Name | select Name,Folder,@{N="IP Address";E={@($_.guest.IPAddress[0])}},@{N="OS Name";E={$_.guest.OSFullName}} | ft -Autosize

} #End MoveVMToSecondNetwork


function history
{
ghy | Select CommandLine,Id, @{n='ExecutionTime';e={$_.EndExecutionTime - $_.StartExecutionTime}} | Sort ExecutionTime -Desc
}


function GetADGroupMembers
{

$MemberList = New-Item -Type file -Force “c:\temp\GroupMembers.csv”
$groups = Get-ADGroup -Filter "Name -like 'AENT*'"
foreach ($group in $groups){
  $members = Get-ADGroupMember -Identity $($group.DistinguishedName) | Select distinguishedName, name
  foreach ($member in $members){
    
    $group.Name + "," + $member.name | Out-File $MemberList -Encoding ASCII -Append
    $nl = [Environment]::NewLine | Out-File $MemberList -Encoding ASCII -Append
  }
}

}



function GetADGroupMemberDetails
{

$MemberList = New-Item -Type file -Force “c:\temp\GroupMembers.csv”

$groups = Get-ADGroup -Filter "Name -like 'SomeGroupName'"
foreach ($group in $groups){
  $members = Get-ADGroupMember -Identity $($group.DistinguishedName) | Select distinguishedName, name
  foreach ($member in $members){
    $memberdetail = Get-QADUser $member.distinguishedName
    $group.Name + "," + $memberdetail.LogonName + "," + $memberdetail.DisplayName + "," + $memberdetail.LogonScript | Out-File $MemberList -Encoding ASCII -Append
    #$group.Name + "," + $member.name | Out-File $MemberList -Encoding ASCII -Append
    $nl = [Environment]::NewLine | Out-File $MemberList -Encoding ASCII -Append
  }
}

}

function LongestPath 
{

$PathToCheck = $args[0]

#Clear variables to be used for comparison
#Clear-Variable m
#Clear-Variable n

#check every path against the longest so far
Get-ChildItem -Recurse -Force $PathToCheck | %{$m=$_.fullname;if($m.length -gt $n.length){$n=$m}};

Write-Host "The longest path length is $($n.length) characters"
Write-Host "The path location is:"
Write-Host $($n)

Remove-Variable m
Remove-Variable n

}


function GetPathsOverLength
{
#http://blogs.technet.com/b/mwiles/archive/2011/10/07/path-is-too-long.aspx
#http://technet.microsoft.com/en-us/library/bb680872.aspx

$PathToCheck = $args[0]
$LengthToCheck = $args[1]
$OutputLocation = $args[2]

#Clear variables to be used for comparison
#Clear-Variable m
#Clear-Variable n

#check every path against the longest so far
Get-ChildItem -Recurse -Force $PathToCheck | 
  Where {$_.FullName.Length -gt $LengthToCheck} | 
  Select FullName, @{N="Path Length";E={$_.FullName.Length}} | Export-CSV -NoTypeInformation $OutputLocation

}

function GetProfiles
{

#setup input
$Computer = $args[0]
#If ($Computer -eq $null){$Computer = "localhost"}

#Setup names to exclude
$NamesToExclude = @(
      "systemprofile",
      "LocalService",
      "NetworkService",
      "DefaultAppPool",
      "Administrator",
      "Public",
      "All Users"
      )
$NamesToExclude = ($NamesToExclude | foreach {$_}) -join '|'

#Find OS Build Number
$OS = Gwmi -Class Win32_OperatingSystem -Computer $Computer | select $_.BuildNumber

#List profiles
If ($OS.BuildNumber -ge 6000)
  {Get-ChildItem \\$computer\c$\users | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude} | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime}
Else 
  {Get-ChildItem \\$computer\c$\windows\profiles | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude}  | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime}

} #GetProfiles




function GetProfilesForAllVMs
{

#setup input
$VMs = get-vm | select Name,Folder,@{N="IPAddress";E={@($_.guest.IPAddress[0])}},@{N="OSName";E={$_.guest.OSFullName}} 

#Setup names to exclude
$NamesToExclude = @(
      "systemprofile",
      "LocalService",
      "NetworkService",
      "DefaultAppPool",
      "Administrator",
      "Public",
      "Temp",			
      "All Users"
      )
$NamesToExclude = ($NamesToExclude | foreach {$_}) -join '|'

#Write to CSV file
$Header = "FullName, LastWriteTime, CreationTime" 
$Header | OutFile "C:\Temp\all_vm_profiles.csv"
$numVMs = $VMs.Length

for($counter=0;$counter -lt $numVMs;$counter++)
{
  $vm = $VMs[$counter].Name
  $vmOS = $VMs[$counter].OSName
  
  Write-Host "Working with $vm as $vmOS, number $counter OF $numVMs"

  If ($vmOS -match "Microsoft Windows XP Professional"){
    Get-ChildItem \\$vm\c$\windows\profiles | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude}  | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime | Export-CSV -Append -NoTypeInformation "C:\Temp\all_vm_profiles.csv"
  }

  If ($vmOS -match "Microsoft Windows 7"){
    Get-ChildItem \\$vm\c$\users | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude} | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime | Export-CSV -Append -NoTypeInformation "C:\Temp\all_vm_profiles.csv"
  }

  If ($vmOS -match "Microsoft Windows 8"){
    Get-ChildItem \\$vm\c$\users | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude} | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime | Export-CSV -Append -NoTypeInformation "C:\Temp\all_vm_profiles.csv"
  }

}


} #GetProfilesForAllVMs


function GetProfilesForMachines
{

#This is ported for physical machines so some variable names reference a VM script

#setup input
$VMs = $args

#Setup names to exclude
$NamesToExclude = @(
      "systemprofile",
      "LocalService",
      "NetworkService",
      "DefaultAppPool",
      "Administrator",
      "Public",
      "Temp",			
      "All Users",
      "Classic .NET AppPool"
      )
$NamesToExclude = ($NamesToExclude | foreach {$_}) -join '|'

#Write to CSV file
$Header = "FullName, LastWriteTime, CreationTime" 
$Header | OutFile "C:\Temp\all_machine_profiles.csv"
$numVMs = $VMs.Length

for($counter=0;$counter -lt $numVMs;$counter++)
{
  $vm = $VMs[$counter].ToString()
  
  Write-Host "Working with $vm , number $($counter+1) OF $numVMs"

#WinXP
# Disabled assuming all are Windows 7
#		Get-ChildItem \\$vm\c$\windows\profiles | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude}  | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime | Export-CSV -Append -NoTypeInformation "C:\Temp\all_machine_profiles.csv"

#Win7 and up
    Get-ChildItem \\$vm\c$\users | Where-Object { $_.psIsContainer -eq $true } | Where {$_.BaseName -notmatch $NamesToExclude} | Sort-Object -Property LastWriteTime -Descending | Select -First 5 FullName, LastWriteTime, CreationTime | Export-CSV -Append -NoTypeInformation "C:\Temp\all_machine_profiles.csv"

}


} #GetProfilesForMachines


function getOrphanVM
{
# Purpose : List all orphaned vmdk on all datastores in all VC's
# Version : v2.0
# Author  : J. Greg Mackinnon, from original by HJA van Bokhoven
# Change  : v1.1  2009.02.14  DE  angepasst an ESX 3.5, Email versenden und Filegrösse ausgeben
# Change  : v1.2  2011.07.12 EN  Updated for ESX 4, collapsed if loops into single conditional
# Change  : v2.0  2011.07.22 EN: 
  # Changed vmdk search to use the VMware.Vim.VmDiskFileQuery object to improve search accuracy
  # Change vmdk matching logic as a result of VmDiskFileQuery usage
  # Pushed discovered orphans into an array of custom PS objects
  # Simplified logging and email output
      
#Initialize the VIToolkit:
#add-pssnapin VMware.VimAutomation.Core
#[Reflection.Assembly]::LoadWithPartialName("VMware.Vim")

#Main

[string]$strVC = "vdiserver.domain.com"								# Virtual Center Server name
[string]$logfile = "c:\temp\getOrphanVMDK.log"
[string]$SMTPServer = "mail.domain.com"							# Change to a SMTP server in your environment
[string]$mailfrom = "email@address.com"	# Change to email address you want emails to be coming from
[string]$mailto = "email@address.com"							# Change to email address you would like to receive emails
[string]$mailreplyto = "email@address.com"						# Change to email address you would like to reply emails

[int]$countOrphaned = 0
[int64]$orphanSize = 0

# vmWare Datastore Browser query parameters
# See http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.host.DatastoreBrowser.SearchSpec.html
$fileQueryFlags = New-Object VMware.Vim.FileQueryFlags
$fileQueryFlags.FileSize = $true
$fileQueryFlags.FileType = $true
$fileQueryFlags.Modification = $true
$searchSpec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchSpec.details = $fileQueryFlags
#The .query property is used to scope the query to only active vmdk files (excluding snaps and change block tracking).
$searchSpec.Query = (New-Object VMware.Vim.VmDiskFileQuery)
#$searchSpec.matchPattern = "*.vmdk" # Alternative VMDK match method.
$searchSpec.sortFoldersFirst = $true

if ([System.IO.File]::Exists($logfile)) {
    Remove-Item $logfile
}

#Time stamp the log file
(Get-Date –f "yyyy-MM-dd HH:mm:ss") + "  Searching Orphaned VMDKs..." | Tee-Object -Variable logdata
$logdata | Out-File -FilePath $logfile -Append
#Connect to vCenter Server
Connect-VIServer $strVC

#Collect array of all VMDK hard disk files in use:
[array]$UsedDisks = Get-View -ViewType VirtualMachine | % {$_.Layout} | % {$_.Disk} | % {$_.DiskFile}
#The following three lines were used before adding the $searchSpec.query property.  We now want to exclude template and snapshot disks from the in-use-disks array.
# [array]$UsedDisks = Get-VM | Get-HardDisk | %{$_.filename}
# $UsedDisks += Get-VM | Get-Snapshot | Get-HardDisk | %{$_.filename}
# $UsedDisks += Get-Template | Get-HardDisk | %{$_.filename}

#Collect array of all Datastores:
#$arrDS is a list of datastores, filtered to exclude ESX local datastores (all of which end with "-local1" in our environment), and our ISO storage datastore.
[array]$allDS = Get-Datastore | select -property name,Id | ? {$_.name -notmatch "-local1"} | ? {$_.name -notmatch "-iso$"} | Sort-Object -Property Name

[array]$orphans = @()
Foreach ($ds in $allDS) {
  "Searching datastore: " + [string]$ds.Name | Tee-Object -Variable logdata
  $logdata | Out-File -FilePath $logfile -Append
  $dsView = Get-View $ds.Id
  $dsBrowser = Get-View $dsView.browser
  $rootPath = "["+$dsView.summary.Name+"]"
  $searchResult = $dsBrowser.SearchDatastoreSubFolders($rootPath, $searchSpec)
  foreach ($folder in $searchResult) {
      foreach ($fileResult in $folder.File) {
      if ($UsedDisks -notcontains ($folder.FolderPath + $fileResult.Path) -and ($fileResult.Path.length -gt 0)) {
        $countOrphaned++
        IF ($countOrphaned -eq 1) {
          ("Orphaned VMDKs Found: ") | Tee-Object -Variable logdata
          $logdata | Out-File -FilePath $logfile -Append
        }
        $orphan = New-Object System.Object
        $orphan | Add-Member -type NoteProperty -name Name -value ($folder.FolderPath + $fileResult.Path)
        $orphan | Add-Member -type NoteProperty -name SizeInGB -value ([Math]::Round($fileResult.FileSize/1gb,2))
        $orphan | Add-Member -type NoteProperty -name LastModified -value ([string]$fileResult.Modification.year + "-" + [string]$fileResult.Modification.month + "-" + [string]$fileResult.Modification.day)
        $orphans += $orphan
        $orphanSize += $fileResult.FileSize
        $orphan | ft -AutoSize | out-string -Width 4096 | Tee-Object -Variable logdata
        $logdata | Out-File -FilePath $logfile -Append
        [string]("Total Size of orphaned files: " + ([Math]::Round($orphanSize/1gb,2)) + " GB") | Tee-Object -Variable logdata
        $logdata | Out-File -FilePath $logfile -Append
        Remove-Variable orphan
      }
    }
  }
}
(Get-Date –f "yyyy-MM-dd HH:mm:ss") + "  Finished (" + $countOrphaned + " Orphaned VMDKs Found.)" | Tee-Object -Variable logdata
$logdata | Out-File -FilePath $logfile -Append

if ($countOrphaned -gt 0) {
  $orphans | Export-CSV -NoTypeInformation c:\temp\orphan.csv
  [string]$body = "Orphaned VMDKs Found: `n"
  $body += $orphans | Sort-Object -Property LastModified | ft -AutoSize | out-string -Width 4096
  $body += [string]("Total Size or orphaned files: " + ([Math]::Round($orphanSize/1gb,2)) + "GB")

    #$SmtpClient = New-Object system.net.mail.smtpClient
    #$SmtpClient.host = $SMTPServer
    #$MailMessage = New-Object system.net.mail.mailmessage
    #$MailMessage.from = $mailfrom
    #$MailMessage.To.add($mailto)
    #$MailMessage.replyto = $mailreplyto
    #$MailMessage.IsBodyHtml = 0
    #$MailMessage.Subject = "Info: VMware orphaned VMDKs"
    #$MailMessage.Body = $body
#	"Mailing report... " | Tee-Object -Variable logdata
#	$logdata | Out-File -FilePath $logfile -Append

    $emailattachment = "c:\temp\orphan.csv"
    $subject = "Info: VMware orphaned VMDKs"

    #$attachment = New-Object System.Net.Mail.Attachment($emailattachment, 'text/plain')
    #$mailmessage.Attachments.Add($attachment)
   
    Send-MailMessage -To $mailto -From $mailfrom -Subject $subject -Body $body -Attachments $emailattachment -SmtpServer $SMTPServer -Encoding ([System.Text.Encoding]::UTF8)

    	#$SmtpClient.Send($MailMessage)
  #$attachment.Dispose()
  #$emailattachment.Dispose()
  #$mailmessage.Dispose()
  
}
}

Function CheckSCCMHealth
{

#taken from:
#http://gallery.technet.microsoft.com/scriptcenter/PowerShell-Script-SCCM-19e8299c

###############################################
## SCCM 2007 R2 HEALTH CHECK
##
## By: Brenton Blawat
## Website Reference: Http://www.bittangents.com
##
##
## Please note that sccm.xml needs to be in the same directory as this script.
##
## Please also note that `r`n means "return -- new line" for the email formatting.
##
###############################################

#To run the script and Email Warning AND Critical Messages:
# 
#(What we use every 7am in the morning)
# 
#powershell.exe directory\sccm_health.ps1 –reporttype “MorningReport”
# 
#To run the script and Email Critical Messages:
# 
#(What we use every hour at the top of every hour)
# 
#powershell.exe directory\sccm_health.ps1
# 
#To send all results regardless of report type:
# 
#Modify SCCM_Health.ps1 line 18 to be if (-not $debug) { $debug = 1 }
# 
#Alternatively you can run:
# 
#powershell.exe directory\sccm_health.ps1 –debug 1

#Get Computer Name For Email Generation
#$CmpName = [System.Net.DNS]::GetHostName()
#Set to SCCM Server instead
$CmpName = "SCCMServer"

# Take any input triggers as the report type MorningReport
# param($reporttype,$debug)

$reporttype = "MorningReport"

#To Turn On Debugging Level Emails Change From 0 to 1 or use the above trigger
if (-not $debug) { $debug = 0 }

# If the report does not contain the following triggers, only output the debugging level and/or on Critical Issues.
if ($reporttype -notcontains "MorningReport") { 
    $reporttype = ""
}



#Function to create a new email
function send-email { param($emailFrom,$emailTo,$subject,$body)
    #Specify the Server Name
    $smtpServer = "mail.domain.com"
    #Setup the Email Connection Object
    $smtp = new-object Net.Mail.SmtpClient($smtpServer)
    #Send the Email as Specified
    $smtp.Send($emailFrom, $emailTo, $subject, $body)
}

# This function will count the files in the directories specified in the XML document.
Function get-filecount { param($inbox, $directory)
  # Generate the count of the child item.
    $count = (Get-Childitem $directory).count
  
    # If the Count is less than 1000 AND
    # If the script is being run as a MorningReport or in debug mode Return Informational.
    If ($count -lt 1000) { if ($reporttype -eq "MorningReport" -or $debug -eq "1") { return "Informational: $inbox File Count is: $Count `r`n" } }
  # If the count is greater than 1000
  If ($count -gt 1000) { 
        # If the count is greater than 10000 Return a Critical Message
    If ($count -gt 10000) { 
      return "***** CRITICAL *****: $inbox File Count is: $Count `r`n"
    }		
    #if it is less than 10000, Return a Warning Message
        Else { if ($reporttype -eq "MorningReport" -or $debug -eq "1") { return "***** WARNING *****: $inbox File Count is: $Count `r`n"  } }
  }
}

# This Function Will Get the Service Status of a service on a computer. It will determine what the startup type
# is and what the current status of the service is.
function get-servicestatus { param($CmpName,$name,$service,$health)
    
    #Create the Result Array Variable
    $serviceresult = @()
    
    # Query the Service Startup Mode of the local computer
    $servicestart = Get-WmiObject -Computername $CmpName -Query "Select StartMode From Win32_Service Where Name='$service'" -ErrorVariable Err -ErrorAction SilentlyContinue
    #if there is an error -- report it here -- This is useful if RPC is not functional or the service is not responding
    if ($err) { 
        $serviceresult += "***** CRITICAL *****: Error Getting Service Start Information: $err `r`n"
        [string]$servicemode = "(Cannot Determine Start Mode)"
    }
    #This will ensure that the service startup type IS what is specified in the SCCM File. If not -- Throw an error.
    if (-not $servicestart) { 
        $serviceresult += "***** CRITICAL *****: Error Getting Service Start Information: $err `r`n"
        [string]$servicemode = "(Cannot Determine Start Mode)"
    }
    # If it is functional, Return the start mode.
    Else { [string]$servicemode = $servicestart.StartMode }
    
    # This query will get the current running state of the service.    
    $service = get-service -Computername $CmpName -name $service -ErrorVariable Err -ErrorAction SilentlyContinue
    # This is the error handling. If the service is not responding -- it will report the error here.
    if ($err) { 
        $serviceresult += "***** CRITICAL *****: Error Getting Service Status Information: $err `r`n"
        [string]$service = "(Cannot Determine Service Running Status)"
    }    
    # If it is functional, Return the running mode.
    Else { [string]$servicestatus = $service.status }
 
    
       
    # If the XML File Reads Started/Automatic for the Service to be Healthy Proceed with the following
    if ($health -eq "Started/Automatic") {
        # Is the current State Set to Auto as expected? If not -- throw Exception
        if ($servicemode -notmatch "Auto") {
            $serviceresult += "***** CRITICAL *****: $Name Service Start Mode Set to: $servicemode and Expecting to be $health `r`n"
        }
        #Report Healthy for MorningReport and Debug.
        else {
            if ($reporttype -eq "MorningReport" -or $debug -eq "1") { $serviceresult += "Informational: $Name Service Start Mode Set to: $servicemode `r`n" }
        }
        ### Phase 2 ###
        # Is the service currently running as expected? If Not -- throw Exception    
        if ($servicestatus -notmatch "Running") {
            $serviceresult += "***** CRITICAL *****: $Name Service Running Status is currently: $servicemode and Expecting to be $health `r`n"
        }
        #Report Healthy for MorningReport and Debug.
        else {
            if ($reporttype -eq "MorningReport" -or $debug -eq "1") { $serviceresult += "Informational: $Name Service Running Status is currently: $servicestatus `r`n" }
        }
    }
    
    #If the XML File Reads Manual Startup for Service to be Healthy Proceed with the following
    if ($health -eq "Manual") {
            # If the service is not set to manual -- throw exception.
            if ($servicemode -notmatch "Manual") {
                $serviceresult += "***** CRITICAL *****: $Name Service Start Mode Set to: $servicemode and Expecting to be $health `r`n"
            }
            #Report Healthy for MorningReport and Debug.
            else {
                if ($reporttype -eq "MorningReport" -or $debug -eq "1") { $serviceresult += "Informational: $Name Service Start Mode Set to: $servicemode `r`n" }
            }
    } 
    
    #If the XML File Reads Disabled Startup for Service to be Healthy Proceed with the following
    if ($health -eq "Disabled") {
        # If the service is not set to disabled -- throw exception.
        if ($servicemode -notmatch "Disabled") {
            $serviceresult += "***** CRITICAL *****: $Name Service Start Mode Set to: $servicemode and Expecting to be $health `r`n"
        }
        #Report Healthy for MorningReport and Debug.        
        else {
            if ($reporttype -eq "MorningReport" -or $debug -eq "1") { $serviceresult += "Informational: $Name Service Start Mode Set to: $servicemode `r`n" }
        }
    }
    return $serviceresult	
}

# This function will read the XML data
function Get-XMLData { param($XMLExtract,$XMLvar)
  
  #Result Variable
  $dataresult = @()
    
    # Get the content from the sccm.xml file
  $xml = [xml] (get-content "c:\temp\sccm.xml")
    
    # Start the DOT Notation Loop for the data specified to extract
  ForEach($node in $xml.GetElementsByTagName("$xmlextract")) {
    $id = $node.Id
    ForEach($item in $node) {
      #Grab the SCCM Path Data if Requested
            if ($xmlextract -eq "SCCMPATH") {
                $xmlpath = $item.objloc
                return $xmlpath
            }
            # Start the File Check Process if Requested
            if ($xmlextract -eq "filecheck") {
                $xmlname = $item.name
                $xmldirpath = $installdir + $item.objloc
                $dataresult += get-filecount $xmlname $xmldirpath
            
            }
            # Start the Service Check Process if Requested
            if ($xmlextract -eq "servicecheck") {
                $xmlname = $item.name
                $xmlservice = $item.service
                $xmlhealth = $item.healthy
                $dataresult += get-servicestatus $CmpName $xmlname $xmlservice $xmlhealth
                    
            }
    }
  }
#Return Results
return $dataresult
}

#Create the Report Variable and header
$report = @()

#Header to Email
$report += "------------------------------------------`r`n"
$report += "-------------SCCM HEALTH CHECK------------`r`n"
$report += "---------------$CmpName---------------`r`n"
$report += "------------------------------------------`r`n"

$report += "`r`n"

#Get the Installation Directory From XML File
$installdir = Get-XMLData SCCMPATH

$report += "-------$installdir--------`r`n"

#Header to Email
$report += "`r`n"
$report += "-------------SCCM HEALTH CHECK [File Check]------------`r`n"
$report += "`r`n"

# Execute the File Check Process and store in the report variable
$report += Get-XMLData filecheck

$report += "`r`n"
$report += "-------------SCCM HEALTH CHECK [Services Check]------------`r`n"
$report += "`r`n"

# Execute the Service Check Process and Store the report in variable
$report += Get-XMLData servicecheck

# If the report contains a warning or critical item, Send Email.
if ($report -MATCH "WARNING" -OR $report -MATCH "CRITICAL") {

  #Send A New Email. Format: new-email From To Subject Body
  $cpuemail = $CmpName + "@domain.com"
  send-email $cpuemail "enail@address.com" "$CmpName Health Summary Report (Errors Detected)" $report
}

# List out Report for Visual Effect
$report

} #End function check SCCM Heath

function get-localadmin {  
param ($strcomputer)  
  
$admins = Gwmi win32_groupuser –computer $strcomputer   
#$admins = $admins |? {$_.groupcomponent –like '*"Administrators"'}  
  
$admins |% {  
$_.partcomponent –match “.+Domain\=(.+)\,Name\=(.+)$” > $nul  
$matches[1].trim('"') + “\” + $matches[2].trim('"')  
}  

Write-Host $admins | FL * -Auto
}


function get-localadministrators {
    param ([string]$computername=$env:computername)

    $computername = $computername.toupper()
    #$ADMINS = get-wmiobject -computername $computername -query "select * from win32_groupuser where GroupComponent=""Win32_Group.Domain='$computername',Name='administrators'""" | % {$_.partcomponent}
    #$ADMINS = get-wmiobject -computername $computername -query "select * from win32_groupuser where GroupComponent=""Win32_Group.Domain='$computername',Name='administrators'"""
   $ADMINS = get-wmiobject -computername $computername -query "select * from win32_groupuser"
    # $ADMINS = get-wmiobject -computername $computername -query "select * from win32_groupuser where GroupComponent=""Win32_Group.Domain='$computername'""" | % {$_.partcomponent}
    $ADMINS | fl *
    foreach ($ADMIN in $ADMINS.partcomponent) {
                $admin = $admin.replace("\\$computername\root\cimv2:Win32_UserAccount.Domain=","") # trims the results for a user
                $admin = $admin.replace("\\$computername\root\cimv2:Win32_Group.Domain=","") # trims the results for a group
                $admin = $admin.replace('",Name="',"\")
                $admin = $admin.REPLACE("""","")#strips the last "

                $objOutput = New-Object PSObject -Property @{
                    Machinename = $computername
        GroupName = $ADMINS.GroupComponent.split(",")[1].replace("""","").split("=")[1]
                    Fullname = ($admin)
                    DomainName  =$admin.split("\")[0]
                    UserName = $admin.split("\")[1]
                }#end object

    $objreport+=@($objoutput)
    }#end for

    return $objreport
}#end function



Function AddSCCMBoundary
{
[Threading.Thread]::CurrentThread.CurrentCulture = 'en-US'
$XLSX = New-Object -ComObject "Excel.Application"
 
$BoundariesXLSXFile = "C:\temp\CM_Boundaries.xlsx"
$Path = (Resolve-Path $BoundariesXLSXFile).Path
$SavePath = $Path -replace ".xl\w*$",".csv"
 
$WorkBook = $XLSX.Workbooks.Open($Path)
$WorkBook.SaveAs($SavePath,6)
$WorkBook.Close($False)
$XLSX.Quit()
 
$Boundaries = Import-Csv $SavePath
 
foreach($Item in $Boundaries)
{
    Switch($item.'Boundary Type')
    {
 
        "IP Subnet" {$Type = 0}
        "Active Directory Site" {$Type = 1}
        "IPv6" {$Type = 2}
        "Ip Address Range" {$Type = 3}
 
    }

    $Arguments = @{DisplayName = $Item.'Display Name'; BoundaryType = $Type; Value = $Item.'Value';SiteCode=$Item.'SiteCode'}
   Write-Host $Item.'Display Name'
  Write-Host $Item.Value
  Write-Host $Type
  Write-Host $Item.SiteCode

  Write-Host $Arguments.DisplayName
  Write-Host $Arguments.BoundaryType
  Write-Host $Arguments.Value
  Write-Host $Arguments.SiteCode
  Set-WmiInstance -Namespace "ROOT\SMS\Site_TOP" -Class SMS_Boundary -Arguments $Arguments -ComputerName CRASSDSCSS01

#Method2
#$WMIConnection = [WMICLASS]"\\SCCMServer\ROOT\SMS\Site_TOP:SMS_Boundary"
#$NewIPSubetNetBoundary = $WMIConnection.psbase.CreateInstance()
#$NewIPSubetNetBoundary.DisplayName = $Item.'Display Name'
#$NewIPSubetNetBoundary.BoundaryType = $Type
#$NewIPSubetNetBoundary.Value = $Item.Value
#$NewIPSubetNetBoundary.Put()


}

} #End AddSCCMBoundaryRange

function Get-VMEvents {
 <#
   .Synopsis

    Get events for an entity or for query all events.

   .Description

    This function returns events for entities. It's very similar to 
  get-vievent cmdlet.Note that get-VMEvent can handle 1 vm at a time.
  You can not send array of vms in this version of the script.

  .Example

    Get-VMEvents 0All -types "VmCreatedEvent","VmDeployedEvent","VmClonedEvent"

    This will receive ALL events of types "VmCreatedEvent","VmDeployedEvent",
  "VmClonedEvent". 
  
   .Example

    Get-VMEvents -name 'vm1' -types "VmCreatedEvent"

    Will ouput creation events for vm : 'vm1'. This was is faster than piping vms from
  get-vm result. There is no need to use get-vm to pass names to get-vmevents.
  Still, it is ok when you will do it, it will make it just a little bit slower <img src="http://s1.wp.com/wp-includes/images/smilies/icon_wink.gif?m=1129645325g" alt=";)" class="wp-smiley" /> 
  
   .Example

    Get-VMEvents -name 'vm1' -category 'warning'

    Will ouput all events for vm : 'vm1'. This was is faster than piping names from
  get-vm cmdlet. Category will make get-vmevent to search only defined category
  events. 
  
   .Example

    get-vm 'vm1' | Get-VMEvents -types "VmCreatedEvent","VmMacAssignedEvent"

    Will display events from vm1 which will be regarding creation events,
  and events when when/which mac address was assigned


  .Parameter VM

    This parameter is a single string representing vm name. It expects single vm name that
  exists in virtual center. At this moment in early script version it will handle only a case
  where there is 1 instance of vm of selected name. In future it will handle multiple as 
  well.
  
   .Parameter types

    If none specified it will return all events. If specified will return
  only events with selected types. For example : "VmCreatedEvent",
  "VmDeployedEvent", "VmMacAssignedEvent" "VmClonedEvent" , etc...
  
  .Parameter category

    Possible categories are : warning, info, error. Please use this parameter if you
  want to filter events.
  
  .Parameter All

    If you will set this parameter, as a result command will query all events from
  virtual center server regarding virtual machines. 

   .Notes

    NAME:  VMEvents

    AUTHOR: Grzegorz Kulikowski

    LASTEDIT: 11/09/2012
  
  NOT WORKING ? #powercli @ irc.freenode.net 

   .Link

http://psvmware.wordpress.com

 #>

param(
[Parameter(ValueFromPipeline=$true)]
[ValidatenotNullOrEmpty()]
$VM,
[String[]]$types,
[string]$category,
[switch]$All
)
    $si=get-view ServiceInstance
    $em= get-view $si.Content.EventManager
    $EventFilterSpec = New-Object VMware.Vim.EventFilterSpec
  $EventFilterSpec.Type = $types
  if($category){
  $EventFilterSpec.Category = $category
  }
  
  if ($VM){
  $EventFilterSpec.Entity = New-Object VMware.Vim.EventFilterSpecByEntity
  switch ($VM) {
  {$_ -is [VMware.Vim.VirtualMachine]} {$VMmoref=$vm.moref}
  {$_ -is [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl]}{$VMmoref=$vm.Extensiondata.moref}
  default {$vmmoref=(get-view -ViewType virtualmachine -Filter @{'name'=$VM}).moref }
  }
  $EventFilterSpec.Entity.Entity = $vmmoref
        $em.QueryEvents($EventFilterSpec) 
  }
  if ($All) {
  $em.QueryEvents($EventFilterSpec)
  }
}

function get-vmcreationdate {
<#
   .Synopsis

    Gets where possible vm creation date.

   .Description

    This function will return object with information about  creation time, method, month,
  creator for particular vm. 
  VMname         : SomeVM12
  CreatedTime    : 8/10/2012 11:48:18 AM
  CreatedMonth   : August
  CreationMethod : Cloned
  Creator         : office\greg
  
  This function will display NoEvent value in properties in case when your VC does no
  longer have information about those particular events, or your vm events no longer have
  entries about being created. If your VC database has longer retension date it is more possible
  that you will find this event. 

  .Example

    Get-VMCreationdate -VMnames "my_vm1","My_otherVM"

    This will return objects that contain creation date information for vms with names
  myvm1 and myvm2
  
   .Example

    Get-VM -Location 'Cluster1' |Get-VMCreationdate

    This will return objects that contain creation date information for vms that are
  located in Cluster1
  
   .Example

    Get-view -viewtype virtualmachine -SearchRoot (get-datacenter 'mydc').id|Get-VMCreationDate

    This will return objects that contain creation date information for vms that are
  located in datacenter container 'mydc'. If you are using this function within existing loop where you
  have vms from get-view cmdlet, you can pass them via pipe or as VMnames parameter.

  .Example

    $report=get-cluster 'cl-01'|Get-VMCreationdate
  $report | export-csv c:\myreport.csv
    Will store all reported creationtimes object in $report array variable and export report to csv file.
  You can also filter the report before writing it to csv file using select
  $report | Where-Object {$_.CreatedMonth -eq "October"} | Select VMName,CreatedMonth
  So that you will see only vms that were created in October.


  .Example
  get-vmcreationdate -VMnames "my_vm1",testvm55
  WARNING: my_vm1 could not be found, typo?
  VMname         : testvm55
  CreatedTime    : 10/5/2012 2:24:03 PM
  CreatedMonth   : October
  CreationMethod : NewVM
  Creator        : home\greg
  In case when you privided vm that does not exists in yor infrastructure, a warning will be displayed.
  You can still store the whole report in $report variable, but it will not include any information about
  missing vm creation dates. A warning will be still displayed only for your information that there was
  probably a typo in the vm name.
  
  .Parameter VMnames

    This parameter should contain virtual machine objects or strings that represents vm
  names. It is possible to feed this function wiith VM objects that come from get-vm or
  from get-view. 


   .Notes

    NAME:  Get-VMCreationdate

    AUTHOR: Grzegorz Kulikowski

    LASTEDIT: 27/11/2012
  
  NOT WORKING ? #powercli @ irc.freenode.net 

   .Link

http://psvmware.wordpress.com

 #>
 
param(
[Parameter(ValueFromPipeline=$true,Mandatory = $true)]
[ValidateNotNullOrEmpty()] 
[Object[]]$VMnames
)
process {
foreach ($vm in $VMnames){
$ReportedVM = ""|Select VMname,CreatedTime,CreatedMonth,CreationMethod,Creator
if ($CollectedEvent=$vm|Get-VMEvents -types 'VmBeingDeployedEvent','VmRegisteredEvent','VmClonedEvent','VmBeingCreatedEvent' -ErrorAction SilentlyContinue)
  {
  if($CollectedEvent.gettype().isArray){$CollectedEvent=$CollectedEvent|?{$_ -is [vmware.vim.VmRegisteredEvent]}}
  $CollectedEventType=$CollectedEvent.gettype().name
  $CollectedEventMonth = "{0:MMMM}" -f $CollectedEvent.CreatedTime
  $CollectedEventCreationDate=$CollectedEvent.CreatedTime
  $CollectedEventCreator=$CollectedEvent.Username
    switch ($CollectedEventType)
    {
    'VmClonedEvent' {$CreationMethod = 'Cloned'} 
    'VmRegisteredEvent' {$CreationMethod = 'RegisteredFromVMX'} 
    'VmBeingDeployedEvent' {$CreationMethod = 'VmFromTemplate'}
    'VmBeingCreatedEvent'  {$CreationMethod = 'NewVM'}
    default {$CreationMethod='Error'}
    }
  $ReportedVM.VMname=$CollectedEvent.vm.Name
  $ReportedVM.CreatedTime=$CollectedEventCreationDate
  $ReportedVM.CreatedMonth=$CollectedEventMonth
  $ReportedVM.CreationMethod=$CreationMethod
  $ReportedVM.Creator=$CollectedEventCreator
  }else {
    if ($?) {
      if($vm -is [VMware.Vim.VirtualMachine]){$ReportedVM.VMname=$vm.name} else {$ReportedVM.VMname=$vm.ToString()}
      $ReportedVM.CreatedTime = 'NoEvent'
      $ReportedVM.CreatedMonth = 'NoEvent'
      $ReportedVM.CreationMethod = 'NoEvent'
      $ReportedVM.Creator = 'NoEvent'
      
    } else {
      $ReportedVM = $null
      Write-Warning "$VM could not be found, typo?"
    }
  }
  $ReportedVM
}
}
}




function Remove-OrphanedData {
<# http://www.lucd.info/2011/04/25/orphaned-files-and-folders-spring-cleaning/
.SYNOPSIS   Remove orphaned folders and VMDK files
.DESCRIPTION   The function searches orphaned folders and VMDK files
   on one or more datastores and reports its findings.
   Optionally the function removes  the orphaned folders   and VMDK files
.NOTES   Author:  Luc Dekens
.PARAMETER Datastore
   One or more datastores.
   The default is to investigate all shared VMFS datastores
.PARAMETER Delete
   A switch that indicates if you want to remove the folders
   and VMDK files
.EXAMPLE
   PS> Remove-OrphanedData -Datastore ds1
.EXAMPLE
  PS> Get-Datastore ds* | Remove-OrphanedData
.EXAMPLE
  PS> Remove-OrphanedData -Datastore $ds -Delete
#>
 
  [CmdletBinding(SupportsShouldProcess=$true)]
 
  param(
  [parameter(Mandatory=$true,ValueFromPipeline=$true)]
  [PSObject[]]$Datastore,
  [switch]$Delete
  )
 
  begin{
    $fldList = @{}
    $hdList = @{}
 
    $fileMgr = Get-View FileManager
  }
 
  process{
    foreach($ds in $Datastore){
      if($ds.GetType().Name -eq "String"){
        $ds = Get-Datastore -Name $ds
      }
      if($ds.Type -eq "VMFS" -and $ds.ExtensionData.Summary.MultipleHostAccess){
        Get-VM -Datastore $ds | %{
          $_.Extensiondata.LayoutEx.File | where{"diskDescriptor","diskExtent" -contains $_.Type} | %{
            $fldList[$_.Name.Split('/')[0]] = $_.Name
            $hdList[$_.Name] = $_.Name
          }
        }
        Get-Template | where {$_.DatastoreIdList -contains $ds.Id} | %{
          $_.Extensiondata.LayoutEx.File | where{"diskDescriptor","diskExtent" -contains $_.Type} | %{
            $fldList[$_.Name.Split('/')[0]] = $_.Name
            $hdList[$_.Name] = $_.Name
          }
        }
 
        $dc = $ds.Datacenter.Extensiondata
 
        $flags = New-Object VMware.Vim.FileQueryFlags
        $flags.FileSize = $true
        $flags.FileType = $true
 
        $disk = New-Object VMware.Vim.VmDiskFileQuery
        $disk.details = New-Object VMware.Vim.VmDiskFileQueryFlags
        $disk.details.capacityKb = $true
        $disk.details.diskExtents = $true
        $disk.details.diskType = $true
        $disk.details.thin = $true
 
        $searchSpec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
        $searchSpec.details = $flags
        $searchSpec.Query += $disk
        $searchSpec.sortFoldersFirst = $true
 
        $dsBrowser = Get-View $ds.ExtensionData.browser
        $rootPath = "[" + $ds.Name + "]"
        $searchResult = $dsBrowser.SearchDatastoreSubFolders($rootPath, $searchSpec)
        foreach($folder in $searchResult){
          if($fldList.ContainsKey($folder.FolderPath.TrimEnd('/'))){
            foreach ($file in $folder.File){
              if(!$hdList.ContainsKey($folder.FolderPath + $file.Path)){
                New-Object PSObject -Property @{
                  Folder = $folder.FolderPath
                  Name = $file.Path
                  Size = $file.FileSize
                  CapacityKB = $file.CapacityKb
                  Thin = $file.Thin
                  Extents = [string]::Join(',',($file.DiskExtents))
                }
                if($Delete){
                  If ($PSCmdlet.ShouldProcess(($folder.FolderPath + " " + $file.Path),"Remove VMDK")){
                    $dsBrowser.DeleteFile($folder.FolderPath + $file.Path)
                  }
                }
              }
            }
          }
          elseif($folder.File | where {"cos.vmdk","esxconsole.vmdk" -notcontains $_.Path}){
            $folder.File | %{
              New-Object PSObject -Property @{
                Folder = $folder.FolderPath
                Name = $_.Path
                Size = $_.FileSize
                CapacityKB = $_.CapacityKB
                Thin = $_.Thin
                Extents = [String]::Join(',',($_.DiskExtents))
              }
            }
            if($Delete){
              if($folder.FolderPath -eq $rootPath){
                $folder.File | %{
                  If ($PSCmdlet.ShouldProcess(($folder.FolderPath + " " + $_.Path),"Remove VMDK")){
                    $dsBrowser.DeleteFile($folder.FolderPath + $_.Path)
                  }
                }
              }
              else{
                If ($PSCmdlet.ShouldProcess($folder.FolderPath,"Remove Folder")){
                  $fileMgr.DeleteDatastoreFile($folder.FolderPath,$dc.MoRef)
                }
              }
            }
          }
        }
      }
    }
  }
}


Function Get-WMILoggedOnUser {
<#
.SYNOPSIS
    Function to get all logged on sessions (numbers) and coresponding users (domain name) on a local or remote system with WMI
  
.DESCRIPTION
    Function to get all logged on sessions (numbers) and coresponding users (domain name) on a local or remote system with WMI
  
  Gets the Win32_LogonSession and the associated Win32_LoggedOnUser information from WMI.
  Matches the user to the session by sessionid
  Creates a link between the LogType number to LogonTypeName name for the session logon type
  
  Returns an PSCustomObject:
    User properties: Domain,Name and SID
    UserSession properties: StartTime,LogonID,LogonType,LogonTypeName and the ComputerName
    
.PARAMETER ComputerName
  Specifies the computer against which you want to run the management operation.
  The value can be a fully qualified domain name, a NetBIOS name, or an IP address.
  Use the local computer name, use localhost, or use a dot (.) to specify the local computer.
  The local computer is the default. When the remote computer is in a different domain from the user,
  you must use a fully qualified domain name. This parameter can also be piped to the cmdlet.
  
.PARAMETER Logontype
  
  Parameter to select the returned logontype(s)
  
  See MSDN documentation for the WMI Win32_LogonSession class Property Logontype
  
  Win32_LogonSession.Logontype is Numeric value that indicates the type of logon session.

  0	Used only by the System account.

  2	Interactive
    Intended for users who are interactively using the machine, such as a user
    being logged on by a terminal server, remote shell, or similar process.

  3	Network
    Intended for high-performance servers to authenticate clear text passwords.
    LogonUser does not cache credentials for this logon type.

  4	Batch
    Intended for batch servers, where processes can be executed on behalf of a user
    without their direct intervention; or for higher performance servers that process many
    clear-text authentication attempts at a time, such as mail or web servers. LogonUser
    does not cache credentials for this logon type.

  5	Service
    Indicates a service-type logon. The account provided must have the service privilege enabled.

  6	Proxy
    Indicates a proxy-type logon.

  7	Unlock
    This logon type is intended for GINA DLLs logging on users who are interactively
    using the machine. This logon type allows a unique audit record to be generated
    that shows when the workstation was unlocked.

  8	NetworkCleartext
    Windows Server 2003 and Windows XP:  Preserves the name and password in the authentication packages,
    allowing the server to make connections to other network servers while impersonating the client.
    This allows a server to accept clear text credentials from a client, call LogonUser, verify that
    the user can access the system across the network, and still communicate with other servers.

  9	NewCredentials
    Windows Server 2003 and Windows XP:  Allows the caller to clone its current
    token and specify new credentials for outbound connections. The new logon session
    has the same local identify, but uses different credentials for other network connections.

  10	RemoteInteractive
    Terminal Services session that is both remote and interactive.

  11	CachedInteractive
    Attempt cached credentials without accessing the network.

  12	CachedRemoteInteractive
    Same as RemoteInteractive. This is used for internal auditing.

  13	CachedUnlock
    Workstation logon.

.PARAMETER Credential
    Allows alternate Credentials to be used in query
    
.EXAMPLE
    Get-WMILoggedOnUser
  
  Returns all types of logged on User sessions on the local machine (computer)
  
.EXAMPLE
    Get-WMILoggedOnUser -Computername 'localhost'  -LogonType 'All'
  
  Same as example 1: Returns all types of logged on User sessions on the local machine (computer)
  
.EXAMPLE
    Get-WMILoggedOnUser -Computername 'Server1','Server2' -LogonType 'Interactive'
  
  Returns only logged on Users with logon type of 'Interactive' (number 2)
  
.NOTES
    Author: Peter Kriegel
    Version 1.1.0 -- Boe Prox
        -Added pipeline support for computername
        -Expanded error handling
        -Allow alternate credentials
    Version 1.0.1 -- GiZmO 33
        -Added Error handling
    Version 1.0.0
        - Initial Creation
    12.July.2013
#>

    [Cmdletbinding()]
    param (
        [Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [Alias('__Server','DNSHostname','IPAddress','Name')]
        [String[]]$Computername = $ENV:COMPUTERNAME,
    
    [Parameter(Position=1)]
    [ValidateSet('0','LocalSystem','2','Interactive','3','Network','4','Batch','5',
    'Service','6','Proxy','7','Unlock','8','NetworkCleartext','9','NewCredentials',
    '10','RemoteInteractive','11','CachedInteractive','12','CachedRemoteInteractive',
    '13','CachedUnlock','All')]
    [String[]]$LogonType = @('0','2','3','4','5','6','7','8','9','10','11','12','13'), # All
        [parameter()]
        [Alias('RunAs')]
        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty     
    )

    Begin{
            #wmiparams
            $wmiParam = @{
                ErrorAction = 'Stop'
            }
            If ($PSBoundParameters['Credential']) {
                $wmiParam.Credential = $Credential
            }
            # define LogOnType hashtable for to convert Numbers into Text
            $HashLogonType = @{ 
                '0'='LocalSystem' 
                '2'='Interactive'
                '3'='Network'
                '4'='Batch'
                '5'='Service'
                '6'='Proxy'
                '7'='Unlock'
                '8'='NetworkCleartext'
                '9'='NewCredentials'
                '10'='RemoteInteractive'
                '11'='CachedInteractive'
                '12'='CachedRemoteInteractive'
                '13'='CachedUnlock'
            }
            
    } # end Begin block

    Process {
        Try {
            ForEach($CurComputerName in $ComputerName) {
                $wmiParam.Computername = $CurComputerName
                $wmiParam.Class = 'Win32_LogonSession'
                $LogonSessions = Get-WmiObject @wmiParam
                $wmiParam.Remove('Class')
                ForEach($LogonSession in $LogonSessions) {
                    Try {
                        $wmiParam.Query = "Associators of {Win32_LogonSession.LogonId=$($LogonSession.LogonId)} Where AssocClass=Win32_LoggedOnUser Role=Dependent"
                        $LoggedOnUser = Get-WmiObject @wmiParam | 
                            Select-Object Domain,Name,SID,StartTime,LogonID,LogonType,LogonTypeName,ComputerName
                        If ($LoggedOnUser.Name) {
                            $LoggedOnUser.StartTime = [Management.ManagementDateTimeConverter]::ToDateTime($LogonSession.starttime)
                            $LoggedOnUser.LogonID = $LogonSession.LogonID
                            $LoggedOnUser.LogonType = $LogonSession.logontype
                            $LoggedOnUser.LogonTypeName = $HashLogonType[[String]$LogonSession.logontype]
                            $LoggedOnUser.ComputerName = $CurComputerName
                
                    # Filter selected LogonTypes to report
                    If($LogonType -contains [String]$LoggedOnUser.LogonType -or $LogonType -contains $LoggedOnUser.LogonTypeName) {
                      # return result object
                      $LoggedOnUser
                    }
                        } Else {
                            Write-Warning ("{0}: Cannot find associated Logon Session!" -f $LogonSession.logonId)
                        }
                    } Catch {
                        Write-Warning ("{0}: {1}" -f $Logonsession.logonid,$_.exception.message)
                    }
                } # end  ForEach $LogonSession
                $wmiParam.Remove('Query')
            }  # end ForEach $Computer 
        } catch [exception] {
            Write-Warning ("{0}" -f $_.exception.message)
        }
    } # end process block
    End {}
}

 

Inventory Mapped Network Printers and Drives With SCCM

A common issue we have is knowing what drives and printers are actively mapped and being used. There are several ways to get usage data via the file server and print servers, but these are not always adequate and fail to produce a complete picture of the user environment. You can currently return local printers as the system account has rights to that data, but anything mapped under the user account will not be accessible.

What I have done is modify some of the work done at https://social.technet.microsoft.com/Forums/en-US/c08c393d-1ea4-4f6b-8f07-affc0f743193/network-printer-inventory-in-system-centre-configuration-manager-sccm-2012 and extend it to allow for multiple users on a machine to have data reported as well as cleaning out anything that is no longer current. This allows an administrator to see how many users and how many machines are connecting to the network drive and printer resources and also report on users attempting to connect to resources that have been decommissioned. When a machine tries connecting to a resource that is no longer available, this impacts the user’s environment and causes degraded performance.

I am setting this up in a SCCM 2012 R2 SP1 environment. This setup should account for both 32 and 64 systems as well as multiple users on a system, such as a Citrix or terminal server environment. All files needed are attached to this posting here, but I will be putting the code blocks inline in case you want to copy and paste or your environment does not permit file downloads.

I did discover during testing, that since this runs as the user, the powershell script will not run unless signed due to the default permissions of restricted. You could of course change powershell’s execution policy to unrestricted, but this is no good for an enterprise, so we’ll check out Scott and Ed’s posts at http://www.hanselman.com/blog/SigningPowerShellScripts.aspx and http://blogs.technet.com/b/heyscriptingguy/archive/2010/06/16/hey-scripting-guy-how-can-i-sign-windows-powershell-scripts-with-an-enterprise-windows-pki-part-1-of-2.aspx and Part 2 on how to sign code, which is the proper way. I also setup a GPO to enable running local and remote signed code to allow the scripts to execute using the information from http://www.techrepublic.com/blog/the-enterprise-cloud/set-the-powershell-execution-policy-via-group-policy/


First you need to extend the configuration.mof file. We just need to add the following code to your configuration.mof and run the command (modify to match your install location at the primary or central site server) (see file Custom_Printer_Drives_Configuration.mof)

%WINDIR%\System32\WBEM\Mofcomp.exe “C:\Program Files\Microsoft Configuration Manager\inboxes\clifiles.src\hinv\configuration.mof”

Paste the codeblock between these lines. You may have material in there already, just add this after your own custom inventory information.

//========================
// Added extensions start
//========================

//========================
// Added extensions end
//========================

//==== Start custom printer and drive reporting ====

#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("MAPPEDDRIVES", NOFAIL)
[dynamic, provider("RegProv"), ClassContext("Local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\SCCMINVENTORY\\MAPPEDDRIVES")]
Class MAPPEDDRIVES
{
[key] string KeyName;
[PropertyContext("UserDomain")] String UserDomain;
[PropertyContext("UserName")] String UserName;
[PropertyContext("ShareName")] String ShareName;
[PropertyContext("DriveLetter")] String DriveLetter;
[PropertyContext("Size")] Uint32 Size;
[PropertyContext("FreeSpace")] Uint32 FreeSpace;
[PropertyContext("System")] String System;
[PropertyContext("FileSystem")] String FileSystem;
[PropertyContext("DateInventoried")] String DateInventoried;
};

#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("MAPPEDDRIVES_64", NOFAIL)
[dynamic, provider("RegProv"), ClassContext("Local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\SCCMINVENTORY\\MAPPEDDRIVES")]
Class MAPPEDDRIVES_64
{
[key] string KeyName;
[PropertyContext("UserDomain")] String UserDomain;
[PropertyContext("UserName")] String UserName;
[PropertyContext("ShareName")] String ShareName;
[PropertyContext("DriveLetter")] String DriveLetter;
[PropertyContext("Size")] Uint32 Size;
[PropertyContext("FreeSpace")] Uint32 FreeSpace;
[PropertyContext("System")] String System;
[PropertyContext("FileSystem")] String FileSystem;
[PropertyContext("DateInventoried")] String DateInventoried;
};

#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("NETWORKPRINTERS", NOFAIL)
[dynamic, provider("RegProv"), ClassContext("Local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\SCCMINVENTORY\\NETWORKPRINTERS")]
Class NETWORKPRINTERS
{
[key] string KeyName;
[PropertyContext("UserDomain")] String UserDomain;
[PropertyContext("UserName")] String UserName;
[PropertyContext("PrintServer")] String PrintServer;
[PropertyContext("PrinterQueue")] String PrinterQueue;
[PropertyContext("PrinterLocation")] String PrinterLocation;
[PropertyContext("PrinterDriver")] String PrinterDriver;
[PropertyContext("PrintProcessor")] String PrintProcessor;
[PropertyContext("PrinterPortName")] String PrinterPortName;
[PropertyContext("DateInventoried")] String DateInventoried;
};

#pragma namespace ("\\\\.\\root\\cimv2")
#pragma deleteclass("NETWORKPRINTERS_64", NOFAIL)
[dynamic, provider("RegProv"), ClassContext("Local|HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\SCCMINVENTORY\\NETWORKPRINTERS")]
Class NETWORKPRINTERS_64
{
[key] string KeyName;
[PropertyContext("UserDomain")] String UserDomain;
[PropertyContext("UserName")] String UserName;
[PropertyContext("PrintServer")] String PrintServer;
[PropertyContext("PrinterQueue")] String PrinterQueue;
[PropertyContext("PrinterLocation")] String PrinterLocation;
[PropertyContext("PrinterDriver")] String PrinterDriver;
[PropertyContext("PrintProcessor")] String PrintProcessor;
[PropertyContext("PrinterPortName")] String PrinterPortName;
[PropertyContext("DateInventoried")] String DateInventoried;
};

//===== End custom printer and drive reporting ======

It should look something like this

edited configuration mof

Recompile_Mof

Then we need to add this to the hardware inventory. This should be as simple as importing the mof file Printer_and_Drive_Inventory_To_Import.mof and making sure everything is checked

#pragma namespace ("\\\\.\\root\\cimv2\\SMS")
#pragma deleteclass("NETWORKPRINTERS", NOFAIL)
[SMS_Report(TRUE),SMS_Group_Name("NETWORKPRINTERS"),SMS_Class_ID("NETWORKPRINTERS"),
SMS_Context_1("__ProviderArchitecture=32|uint32"),
SMS_Context_2("__RequiredArchitecture=true|boolean")]
Class NETWORKPRINTERS: SMS_Class_Template
{
[SMS_Report(TRUE),key] string KeyName;
[SMS_Report(TRUE)] String UserDomain;
[SMS_Report(TRUE)] String UserName;
[SMS_Report(TRUE)] String PrintServer;
[SMS_Report(TRUE)] String PrinterQueue;
[SMS_Report(TRUE)] String PrinterLocation;
[SMS_Report(TRUE)] String PrinterDriver;
[SMS_Report(TRUE)] String PrintProcessor;
[SMS_Report(TRUE)] String PrinterPortName;
[SMS_Report(TRUE)] String DateInventoried;
};

#pragma namespace ("\\\\.\\root\\cimv2\\SMS")
#pragma deleteclass("NETWORKPRINTERS_64", NOFAIL)
[SMS_Report(TRUE),SMS_Group_Name("NETWORKPRINTERS64"),SMS_Class_ID("NETWORKPRINTERS64"),
SMS_Context_1("__ProviderArchitecture=64|uint32"),
SMS_Context_2("__RequiredArchitecture=true|boolean")]
Class NETWORKPRINTERS_64 : SMS_Class_Template
{
[SMS_Report(TRUE),key] string KeyName;
[SMS_Report(TRUE)] String UserDomain;
[SMS_Report(TRUE)] String UserName;
[SMS_Report(TRUE)] String PrintServer;
[SMS_Report(TRUE)] String PrinterQueue;
[SMS_Report(TRUE)] String PrinterLocation;
[SMS_Report(TRUE)] String PrinterDriver;
[SMS_Report(TRUE)] String PrintProcessor;
[SMS_Report(TRUE)] String PrinterPortName;
[SMS_Report(TRUE)] String DateInventoried;
};

#pragma namespace ("\\\\.\\root\\cimv2\\SMS")
#pragma deleteclass("MAPPEDDRIVES", NOFAIL)
[SMS_Report(TRUE),SMS_Group_Name("MAPPEDDRIVES"),SMS_Class_ID("MAPPEDDRIVES"),
SMS_Context_1("__ProviderArchitecture=32|uint32"),
SMS_Context_2("__RequiredArchitecture=true|boolean")]
Class MAPPEDDRIVES: SMS_Class_Template
{
[SMS_Report(TRUE),key] string KeyName;
[SMS_Report(TRUE)] String UserDomain;
[SMS_Report(TRUE)] String UserName;
[SMS_Report(TRUE)] String ShareName;
[SMS_Report(TRUE)] String DriveLetter;
[SMS_Report(TRUE)] Uint32 Size;
[SMS_Report(TRUE)] Uint32 FreeSpace;
[SMS_Report(TRUE)] String System;
[SMS_Report(TRUE)] String FileSystem;
[SMS_Report(TRUE)] String DateInventoried;
};

#pragma namespace ("\\\\.\\root\\cimv2\\SMS")
#pragma deleteclass("MAPPEDDRIVES_64", NOFAIL)
[SMS_Report(TRUE),SMS_Group_Name("MAPPEDDRIVES64"),SMS_Class_ID("MAPPEDDRIVES64"),
SMS_Context_1("__ProviderArchitecture=64|uint32"),
SMS_Context_2("__RequiredArchitecture=true|boolean")]
Class MAPPEDDRIVES_64 : SMS_Class_Template
{
[SMS_Report(TRUE),key] string KeyName;
[SMS_Report(TRUE)] String UserDomain;
[SMS_Report(TRUE)] String UserName;
[SMS_Report(TRUE)] String ShareName;
[SMS_Report(TRUE)] String DriveLetter;
[SMS_Report(TRUE)] Uint32 Size;
[SMS_Report(TRUE)] Uint32 FreeSpace;
[SMS_Report(TRUE)] String System;
[SMS_Report(TRUE)] String FileSystem;
[SMS_Report(TRUE)] String DateInventoried;
};

import_window

Imported_Inventory

Then we need to create a script to generate a registry setting to write the data to and assign permissions that will allow users to write this location (see file: Create_Registry_Inventory.ps1)

if (!(Test-Path HKLM:\SOFTWARE\SCCMINVENTORY)) {new-item HKLM:\SOFTWARE\SCCMINVENTORY  -ErrorAction SilentlyContinue}
$perm = get-acl HKLM:\SOFTWARE\SCCMINVENTORY  -ErrorAction SilentlyContinue
$rule = New-Object System.Security.AccessControl.RegistryAccessRule("Authenticated Users","FullControl", "ContainerInherit, ObjectInherit", "InheritOnly", "Allow")  -ErrorAction SilentlyContinue
$perm.SetAccessRule($rule)
Set-Acl -Path HKLM:\SOFTWARE\SCCMINVENTORY $perm  -ErrorAction SilentlyContinue
if (!(Test-Path HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS)) {new-item HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS -ErrorAction SilentlyContinue}
if (!(Test-Path HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES)) {new-item HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES -ErrorAction SilentlyContinue}

You can setup a package to run this file on all machines in your environment once or limit to a smaller collection.

I am using the command line as follows and all files for this project will be in the same folder as they are small, but you can split them into multiple folders or create multiple programs and advertise each individually.

powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File .\Create_Registry_Inventory.ps1

step 1 - create registry package

step 2 - create command line

Then setup a program for the user piece using the following command line (see file Printer_Drive_Inventory.ps1):

powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File .\Printer_Drive_Inventory.ps1

Step 3 - setup user program

Step 4- two programs setup

Then setup the deployment and schedule to run only if previously failed

step 5 - create setup deployment

step 6 - specify collection

step 7 - schedule

step 8 - content options

Then setup the deployment for the user piece. This will run the following code

# https://social.technet.microsoft.com/Forums/en-US/c08c393d-1ea4-4f6b-8f07-affc0f743193/network-printer-inventory-in-system-centre-configuration-manager-sccm-2012?forum=configmanagergeneral#c08c393d-1ea4-4f6b-8f07-affc0f743193
# http://blogs.technet.com/b/breben/archive/2013/08/26/inventory-mapped-drives-in-configmgr-2012.aspx

# run with user rights
# PowerShell.exe -NonInteractive -WindowStyle Hidden -noprofile -ExecutionPolicy Bypass -file .\Printer_Drive_Inventory.ps1

$printers = Get-WMIObject -class Win32_Printer -ErrorAction SilentlyContinue|select-Object -Property ServerName,ShareName,Location,DriverName,PrintProcessor,PortName,Local |Where-Object {$_.Local -ne $true}|Where-Object {$_.ServerName.length -gt 2} -ErrorAction SilentlyContinue
$user = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name).Replace('\','-')

#Remove previous entries
Get-ChildItem -Path HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\ -Recurse -Include $user* -ErrorAction SilentlyContinue | Remove-Item

ForEach($printer in $printers){

Try {
    $PServerName= $printer.ServerName -replace ('\\','')
    $PShareName = $printer.ShareName
    $PLocation = $printer.Location
    $PDriverName = $printer.DriverName
    $PPrintProcessor = $printer.PrintProcessor
    $PPortName = $printer.PortName

    if ((Test-Path HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS)) {
        if ((Test-Path "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName")) {
            Remove-item "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Force -ErrorAction SilentlyContinue
        }
        New-item "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "UserDomain" -Value $user.Split('-')[0] -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "UserName" -Value $user.Split('-')[1] -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "PrintServer" -Value $PServerName -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "PrinterQueue" -Value $PShareName -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "PrinterLocation" -Value $PLocation -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "PrinterDriver" -Value $PDriverName -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "PrintProcessor" -Value $PPrintProcessor -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "PrinterPortName" -Value $PPortName -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\NETWORKPRINTERS\$user $PShareName on $PServerName" -Name "DateInventoried" -Value $(get-date) -PropertyType "String" -ErrorAction SilentlyContinue
    } # End If
    } # End Try
Catch  {}
} #End For Each


#now inventory drives
$drives = Get-WMIObject -class Win32_MappedLogicalDisk -ErrorAction SilentlyContinue|select-Object -Property Caption,Name,FreeSpace,ProviderName,Size,SystemName,FileSystem |Where-Object {$_.Local -ne $true}|Where-Object {$_.ProviderName.length -gt 3} -ErrorAction SilentlyContinue

#Remove previous entries
Get-ChildItem -Path HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\ -Recurse -Include $user* -ErrorAction SilentlyContinue | Remove-Item

ForEach($drive in $drives){
Try {
    $DShareName = $drive.ProviderName -Replace ('\\','\')
    $DName = $drive.Name
    #convert to GB
    $DSize = $drive.Size/1000000000
    $DFreeSpace = $drive.FreeSpace/1000000000
    $DSystem = $drive.SystemName
    $DFileSystem = $drive.FileSystem

    if ((Test-Path HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES)) {
        if ((Test-Path "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName")) {
            Remove-item "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Force -ErrorAction SilentlyContinue
        }
        New-item "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "UserDomain" -Value $user.Split('-')[0] -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "UserName" -Value $user.Split('-')[1] -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "ShareName" -Value $DShareName -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "DriveLetter" -Value $DName -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "Size" -Value $DSize -PropertyType "DWord" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "FreeSpace" -Value $DFreeSpace -PropertyType "DWord" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "System" -Value $DSystem -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "FileSystem" -Value $DFileSystem -PropertyType "String" -ErrorAction SilentlyContinue
        New-ItemProperty "HKLM:\SOFTWARE\SCCMINVENTORY\MAPPEDDRIVES\$user $DName" -Name "DateInventoried" -Value $(get-date) -PropertyType "String" -ErrorAction SilentlyContinue
    } # End If
    } # End Try
Catch {}
} #End For Each

step 9 - deploy user piece

Assign a collection

step 10 - assign to collection

Setting the schedule to run every 4 hours should capture most use cases. You can accomplish this with a login script to call this file also

custom schedule 2

custom schedule

Also go back in and make sure to set it to run once for each user that is logged in and that the Run setting under the General tab is set to Hidden

run once for every user

So now we have 2 deployments

2 deployments

Then, provided everything is working, you should start to see data come in. Remember this is setup to run every 4 hours, but we’re patient and looking for environmental data for a large group of people.

You can then query the data in SQL and write SSRS reports to show this data

Mapped drives can be uncovered here

SELECT [MachineID]
      ,[InstanceKey]
      ,[TimeKey]
      ,[RevisionID]
      ,[AgentID]
      ,[rowversion]
      ,[DateInventoried00]
      ,[DriveLetter00]
      ,[FileSystem00]
      ,[FreeSpace00]
      ,[KeyName00]
      ,[ShareName00]
      ,[Size00]
      ,[System00]
      ,[UserDomain00]
      ,[UserName00]
  FROM [MAPPEDDRIVES_DATA]

  Union

  SELECT [MachineID]
      ,[InstanceKey]
      ,[TimeKey]
      ,[RevisionID]
      ,[AgentID]
      ,[rowversion]
      ,[DateInventoried00]
      ,[DriveLetter00]
      ,[FileSystem00]
      ,[FreeSpace00]
      ,[KeyName00]
      ,[ShareName00]
      ,[Size00]
      ,[System00]
      ,[UserDomain00]
      ,[UserName00]
  FROM [MAPPEDDRIVES64_DATA]

reported_data

And printers can be found here

SELECT [MachineID]
      ,[InstanceKey]
      ,[TimeKey]
      ,[RevisionID]
      ,[AgentID]
      ,[rowversion]
      ,[DateInventoried00]
      ,[KeyName00]
      ,[PrinterDriver00]
      ,[PrinterLocation00]
      ,[PrinterPortName00]
      ,[PrinterQueue00]
      ,[PrintProcessor00]
      ,[PrintServer00]
      ,[UserDomain00]
      ,[UserName00]
  FROM [NETWORKPRINTERS_DATA]

  Union

  SELECT [MachineID]
      ,[InstanceKey]
      ,[TimeKey]
      ,[RevisionID]
      ,[AgentID]
      ,[rowversion]
      ,[DateInventoried00]
      ,[KeyName00]
      ,[PrinterDriver00]
      ,[PrinterLocation00]
      ,[PrinterPortName00]
      ,[PrinterQueue00]
      ,[PrintProcessor00]
      ,[PrintServer00]
      ,[UserDomain00]
      ,[UserName00]
  FROM [NETWORKPRINTERS64_DATA]

reported_data_printers

Or the even easier queries of the following which just throws everything on the screen

SELECT * FROM [MAPPEDDRIVES_DATA]
SELECT * FROM [MAPPEDDRIVES64_DATA]
Select * From [NETWORKPRINTERS_DATA]
Select * From [NETWORKPRINTERS64_DATA]

multiple sql

This is obviously a small environment. Use at your own caution and test before deployment into production. I do need to add in some filtering so it does not populate some obviously bad data. There are a lot of moving parts here, so I would expect issues, but I hope this helps you get started.

Let me know if you have any questions or suggestions. Please email eric@holzhueter.us


Files if you missed the link in the document: http://plainlytechnical.com/files/Printer_And_Drive_Inventory.zip


Edited script text for Printer_Drive_Inventory.ps1 on 5/19/15 to a filter share and server information that is blank as this does no help in reporting. There is also a reg key to increase the MIF file size to 50MB if needed.

Edited script test for Printer_Drive_Inventory.ps1 on 5/21 to add a try/catch into the logic. This will require Windows 7 and Server 2008 as minimum requirements with this change. Those lines can be removed for older environments.