diff --git a/Connect-CmRemoteTools.ps1 b/Connect-CmRemoteTools.ps1
new file mode 100644
index 0000000..5687e71
--- /dev/null
+++ b/Connect-CmRemoteTools.ps1
@@ -0,0 +1,50 @@
+<#
+.SYNOPSIS
+ This script calls the SCCM console's remote tools clients (CmRcViewer.exe) to start a SCCM
+ remote tools console from Powershell.
+.NOTES
+ Created on: 12/9/2014
+ Created by: Adam Bertram
+ Filename: Connect-CmRemoteTools.ps1
+ Requirements: An available SCCM 2012 site server and the SCCM console installed
+ Permissions to connect to the remote computer
+.EXAMPLE
+ PS> .\Connect-CmRemoteTools.ps1 -Computername MYCOMPUTER
+
+ This example would bring up the SCCM remote tools console window connecting to the computer called MYCOMPUTER
+.PARAMETER Computername
+ The name of the computer you'd like to use remote tools to connect to
+.PARAMETER
+ The name of the SCCM site server holding the site database
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateScript({Test-Connection -ComputerName $_ -Quiet -Count 1})]
+ [string]$Computername,
+ [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
+ [string]$SiteServer = 'CONFIGMANAGER'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+}
+
+process {
+ try {
+ ## Find the path of the admin console to get the path of the remote tools client
+ if (!$env:SMS_ADMIN_UI_PATH -or !(Test-Path "$($env:SMS_ADMIN_UI_PATH)\CmRcViewer.exe")) {
+ throw "Unable to find the SCCM remote tools exe. Is the console installed?"
+ } else {
+ $RemoteToolsFilePath = "$($env:SMS_ADMIN_UI_PATH)\CmRcViewer.exe"
+ }
+
+ & $RemoteToolsFilePath $Computername "\\$SiteServer"
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Convert-CMApplicationToPackage.ps1 b/Convert-CMApplicationToPackage.ps1
new file mode 100644
index 0000000..4c8bdc6
--- /dev/null
+++ b/Convert-CMApplicationToPackage.ps1
@@ -0,0 +1,303 @@
+#Requires -Version 3
+#Requires -Module ConfigurationManager
+
+<#
+.SYNOPSIS
+ This creates a ConfigMgr package/program for each deployment type from the attributes of a ConfigMgr application.
+.DESCRIPTION
+ This reads a ConfigMgr application and gathers all needed attributes from the application itself and it's
+ deployment types. It then creates a package/program for each deployment type in the application. If more than one
+ deployment type exists the resulting package(s) will be named $ApplicationName - $DeploymentTypeName" with each
+ program being named $DeploymentTypeName.
+.NOTES
+ Created on: 07/03/2014
+ Created by: Adam Bertram
+ Filename: Convert-CMApplicationToPackage.ps1
+ Credits: http://www.david-obrien.net/2014/01/24/convert-configmgr-applications-packages-powershell/
+ Todos: If the app has a product code, use this for installation program management in the program
+ Create an option to distribute the package to DPs after creation
+.DESCRIPTION
+ This gets all common attributes of a ConfigMgr application that a ConfigMgr package/program has and uses
+ these attributes to create a new packages with a program inside for each application deployment type.
+.EXAMPLE
+ .\Convert-CMApplicationToPackage.ps1 -ApplicationName 'Application 1'
+ This example converts the application "Application 1" into a package called "Application 1" and a program
+ called "Install Application 1" if it has a single deployment type.
+.EXAMPLE
+ .\Convert-CMApplicationToPackage.ps1 -ApplicationName 'Application 1' -SkipRequirements
+ This example converts the application "Application 1" into a package called "Application 1" and a program
+ called Install "Application 1" excluding disk space and OS requirements if it has a single deployment type.
+.PARAMETER ApplicationName
+ This is the name of the application you'd like to convert.
+.PARAMETER PackageName
+ This is the name of the package you'd like to create. If this param isn't used and only 1 deployment type
+ exists in the application, it will default to the name of the application else if the application has multiple
+ deployment types it will default to the application name and the name of the deployment type.
+.PARAMETER SkipRequirement
+ Use this switch parameter if you don't want to bring over any disk or OS requirements from the application.
+.PARAMETER DistributeContent
+ Use this swtich to find all DPs/DP Groups the application is distributed to and distribute the package to them after
+ the package has been created.
+.PARAMETER OsdFriendlyPowershellSyntax
+ Use this switch parameter to convert any program that's simply a reference to a PS1 file that normally works
+ in a non-OSD environment to a full powershell syntax using powershell.exe.
+.PARAMETER AdditionalOptions
+ An array of hashtables of any additional options that will be applied to the resulting package. Use the form
+ @(@{'Package' = @{ 'Property' = 'Value' } }) or @(@{'Program' = @{ 'Property' = 'Value' } })
+.PARAMETER SiteServerName
+ The ConfigMgr site server name
+.PARAMETER SiteCode
+ The ConfigMgr site code.
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string]$ApplicationName,
+ [string]$PackageName,
+ [switch]$SkipRequirements,
+ [switch]$DistributeContent,
+ [switch]$OsdFriendlyPowershellSyntax,
+ [array]$AdditionalOptions = @(@{'Package' = @{ 'PkgFlags' = '128' } }),
+ [string]$SiteServer = 'CONFIGMANAGER',
+ [string]$SiteCode = 'UHP'
+)
+
+begin {
+ try {
+ ## This helper function gets all of the supported platform objects that's supported for creating OS requirements for a package,
+ ## looks for a match between each CI_UniqueID and the OS string and if there's a match, creates a new lazy property instance
+ ## populates the necessary values and returns an array of objects that can be used to populated the SupportOperatingSystemPlatforms
+ ## lazy WMI property on the SMS_Program object.
+ function New-SupportedOsObject([Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.Rule]$OsRequirement) {
+ $SupportedPlatforms = Get-WmiObject -ComputerName $SiteServer -Class SMS_SupportedPlatforms -Namespace "root\sms\site_$SiteCode"
+ $SupportedOs = @()
+ ## Define the array of OS strings to convert to objects
+ if ($OsRequirement.Expression.Operator.OperatorName -eq 'OneOf') {
+ $AppOsList = $OsRequirement.Expression.Operands.RuleId
+ } elseif ($OsRequirement.Expression.Operator.OperatorName -eq 'NoneOf') {
+ ## TODO: Query the site server for all possible operating system values and remove all OSes in
+ ## $OsRequirement.DeploymentTypes[0].Requirements[0].Expression.Operands.RuleId
+ return $false
+ }
+ foreach ($AppOs in $AppOsList) {
+ foreach ($OsDetail in $SupportedPlatforms) {
+ if ($AppOs -eq $OsDetail.CI_UniqueId) {
+ $instance = ([wmiclass]("\\$SiteServer\root\sms\site_$SiteCode`:SMS_OS_Details")).CreateInstance()
+ if ($instance -is [System.Management.ManagementBaseObject]) {
+ $instance.MaxVersion = $OsDetail.OSMaxVersion
+ $instance.MinVersion = $OsDetail.OSMinVersion
+ $instance.Name = $OsDetail.OSName
+ $instance.Platform = $OsDetail.OSPlatform
+ $SupportedOs += $instance
+ }
+ }
+ }
+ }
+ $SupportedOs
+ }
+
+ function Convert-NalPathToName ($NalPath) {
+ $NalPath.Split('\\')[2].Split('.')[0]
+ }
+
+ function Get-DpsinDpGroup ($GroupId) {
+ $Dps = Get-WmiObject @SiteServerWmiProps -Class SMS_DPGroupMembers -Filter "GroupID = '$GroupId'"
+ if ($Dps) {
+ $Dps.DPNALPath | foreach { Convert-NalPathToName $_ }
+ } else {
+ $false
+ }
+ }
+
+ if (!(Test-Path "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1")) {
+ throw 'Configuration Manager module not found. Is the admin console intalled?'
+ } elseif (!(Get-Module 'ConfigurationManager')) {
+ Import-Module "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1"
+ }
+ $Location = (Get-Location).Path
+ Set-Location "$($SiteCode):"
+
+ $Application = Get-CMApplication -Name $ApplicationName
+ if (!$Application) {
+ throw "$ApplicationName not found"
+ }
+ $ApplicationXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($Application.SDMPackageXML)
+
+ $SetProgramProps = @{ }
+
+ $SiteServerWmiProps = @{
+ 'Computername' = $SiteServer;
+ 'Namespace' = "root\sms\site_$SiteCode"
+ }
+
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+process {
+ try {
+ $DeploymentTypes = $ApplicationXML.DeploymentTypes
+
+ for ($i = 0; $i -lt $DeploymentTypes.Count; $i++) {
+ if ($DeploymentTypes.Count -gt 1) {
+ $PackageName = "$ApplicationName - $($ApplicationXML.DeploymentTypes[$i].Title)"
+ } elseif (!$PackageName) {
+ $PackageName = $ApplicationName
+ }
+ $ProgramName = $ApplicationXML.DeploymentTypes[$i].Title
+
+ if (Get-CMPackage -Name $PackageName) {
+ throw "$PackageName already exists"
+ }
+
+ $PackageProps = @{
+ 'Name' = $PackageName;
+ 'Version' = $ApplicationXML.SoftwareVersion;
+ 'Manufacturer' = $ApplicationXML.Publisher;
+ 'Path' = $ApplicationXML.DeploymentTypes[$i].Installer.Contents.Location;
+ }
+
+ ## 07/03/2014 - Even though the New-CMProgram documentation leads you to believe you can use a string for the RunType
+ ## param, it won't work. You must use the [Microsoft.ConfigurationManagement.Cmdlets.AppModel.Commands.RunType] object.
+ $NewProgramProps = @{
+ 'StandardProgramName' = $ProgramName;
+ 'PackageName' = $PackageName;
+ 'RunType' = [Microsoft.ConfigurationManagement.Cmdlets.AppModel.Commands.RunType]::($ApplicationXML.DeploymentTypes[$i].Installer.UserInteractionMode)
+ }
+
+ $AppCmdLine = $ApplicationXML.DeploymentTypes[$i].Installer.InstallCommandLine
+ ## If the command line is simply a reference to a single PS1 file
+ if ($OsdFriendlyPowershellSyntax.IsPresent -and ($AppCmdLine -match '.ps1$')) {
+ $NewProgramProps.CommandLine = "powershell.exe -ExecutionPolicy bypass -NoProfile -NoLogo -NonInteractive -File $AppCmdLine"
+ } else {
+ $NewProgramProps.CommandLine = $ApplicationXML.DeploymentTypes[$i].Installer.InstallCommandLine
+ }
+
+ $SetProgramProps = @{
+ 'EnableTaskSequence' = $true;
+ 'StandardProgramName' = $ProgramName;
+ 'Name' = $PackageName;
+ }
+
+ ## 07/03/2014 - Due to a bug in the New-CMprogram cmdlet, even though 15 min or 720 min is allowed via the GUI
+ ## for the max run time, it doesn't work via the New-CMProgram cmdlet. To compensate, I'm adding or removing
+ ## 1 minute and this works.
+ $Duration = $ApplicationXML.DeploymentTypes[$i].Installer.MaxExecuteTime
+ if ($Duration -eq 15) {
+ $Duration = $Duration + 1
+ } elseif ($Duration -eq 720) {
+ $Duration = $Duration - 1
+ }
+ $NewProgramProps.Duration = $Duration
+
+ if (!$SkipRequirements.IsPresent) {
+ $Requirements = $ApplicationXML.DeploymentTypes[$i].Requirements
+ $RequirementExpressions = $Requirements.Expression
+ $FreeSpaceRequirement = $RequirementExpressions | where { ($_.Operands.LogicalName -contains 'FreeDiskSpace') -and ($_.Operator.OperatorName -eq 'GreaterEquals') }
+ if ($FreeSpaceRequirement) {
+ $NewProgramProps.DiskSpaceRequirement = $FreeSpaceRequirement.Operands.value / 1MB
+ $NewProgramProps.DiskSpaceUnit = 'MB'
+ }
+ }
+
+ switch ($ApplicationXML.DeploymentTypes[$i].Installer.RequiresLogon) {
+ $false {
+ $NewProgramProps.ProgramRunType = 'OnlyWhenNoUserIsLoggedOn'
+ }
+ $true {
+ $NewProgramProps.ProgramRunType = 'OnlyWhenUserIsLoggedOn'
+ }
+ default {
+ $NewProgramProps.ProgramRunType = 'WhetherOrNotUserIsLoggedOn'
+ }
+ }
+
+ if ($ApplicationXML.DeploymentTypes[$i].Installer.UserInteractionMode -eq 'Hidden') {
+ $SetProgramProps['SuppressProgramNotifications'] = $true
+ }
+
+ if ($ApplicationXML.DeploymentTypes[$i].Installer.SourceUpdateCode) {
+ ##TODO: Look into setting installation source management on the package
+ }
+
+ $PostIntallBehavior = $ApplicationXML.DeploymentTypes[$i].Installer.PostInstallBehavior
+ if (($PostIntallBehavior -eq 'BasedOnExitCode') -or ($PostIntallBehavior -eq 'NoAction')) {
+ $SetProgramProps.AfterRunningType = 'NoActionRequired'
+ } elseif ($PostIntallBehavior -eq 'ProgramReboot') {
+ $SetProgramProps.AfterRunningType = 'ProgramControlsRestart'
+ } elseif ($PostIntallBehavior -eq 'ForceReboot') {
+ $SetProgramProps.AfterRunningType = 'ConfigurationManagerRestartsComputer'
+ }
+
+ $NewPackage = New-CMPackage @PackageProps
+ Write-Verbose "Successfully created package name $($NewPackage.Name) ($($NewPackage.PackageID))"
+ $NewProgram = New-CMProgram @NewProgramProps
+ Set-CMProgram @SetProgramProps
+
+ if (!$SkipRequirements.IsPresent) {
+ $OsRequirement = $Requirements | where { $_.Expression -is [Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.OperatingSystemExpression] }
+ if ($OsRequirement) {
+ $SupportedOs = New-SupportedOsObject $OsRequirement
+ $NewProgram.SupportedOperatingSystems = $SupportedOs
+ $NewProgram.Put()
+ }
+ }
+
+ if ($AdditionalOptions) {
+ $AdditionalOptions | foreach {
+ $_.GetEnumerator() | foreach {
+ if ($_.Key -eq 'Package') {
+ $_.Value.GetEnumerator() | foreach {
+ $NewPackage.($_.Key) = $_.Value
+ $NewPackage.Put()
+ }
+ } elseif ($_.Key -eq 'Program') {
+ $_.Value.GetEnumerator() | foreach {
+ $NewProgram.($_.Key) = $_.Value
+ $NewProgram.Put()
+ }
+ }
+ }
+ }
+ }
+
+ if ($DistributeContent.IsPresent) {
+ ## Distribute the converted package to all DP groups the application is a part of
+ $AllDpGroupPackages = Get-WmiObject @SiteServerWmiProps -Class SMS_DPGroupPackages
+ $AllDpGroups = Get-WmiObject @SiteServerWmiProps -Class SMS_DistributionPointGroup
+
+ ## TODO: This currently doesn't support applications in multiple groups
+ $AppDpGroupId = ($AllDpGroupPackages | where { $_.PkgID -eq $Application.PackageID } | Group-Object GroupId).Name
+ $AppDpGroup = $AllDpGroups | where { $_.GroupID -eq $AppDpGroupId}
+ if ($AppDpGroup) {
+ Write-Verbose "Application is in a DP group"
+ Start-CMContentDistribution -DistributionPointGroupName $AppDpGroup.Name -PackageName $PackageName
+ $DpsInAppDpGroup = Get-DpsinDpGroup $AppDpGroupId
+ $SingleDps = Get-WmiObject @SiteServerWmiProps -Class SMS_DistributionPoint -Filter "SecureObjectID = '$($Application.ModelName)'" | where { $DpsInAppDpGroup -notcontains (Convert-NalPathToName $_.ServerNALPath) }
+ } else {
+ $SingleDps = Get-WmiObject @SiteServerWmiProps -Class SMS_DistributionPoint -Filter "SecureObjectID = '$($Application.ModelName)'"
+ }
+
+ if ($SingleDps) {
+ Write-Verbose "Application is in $($SingleDps.Count) single DPs"
+ foreach ($Dp in $SingleDps) {
+ $DpName = Convert-DpNalPathToName $Dp.ServerNALPath
+ Write-Verbose "Adding package '$PackageName' to DP '$DpName'"
+ Start-CMContentDistribution -DistributionPoint $DpName -PackageName $PackageName
+ }
+ }
+ }
+ Unlock-CMObject $NewPackage
+ }
+
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+}
+
+end {
+ Set-Location $Location
+}
\ No newline at end of file
diff --git a/Convert-DynamicDnsRecordToStatic.ps1 b/Convert-DynamicDnsRecordToStatic.ps1
new file mode 100644
index 0000000..a2f8dbb
--- /dev/null
+++ b/Convert-DynamicDnsRecordToStatic.ps1
@@ -0,0 +1,64 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename:
+ Credits:
+ Requirements:
+ Todos:
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER PARAM1
+
+.PARAMETER PARAM2
+
+#>
+[CmdletBinding(DefaultParameterSetName = 'name')]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [Parameter(ParameterSetName = 'name',
+ Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateSet("Tom","Dick","Jane")]
+ [ValidateRange(21,65)]
+ [ValidateScript({Test-Path $_ -PathType 'Container'})]
+ [ValidateNotNullOrEmpty()]
+ [ValidateCount(1,5)]
+ [ValidateLength(1,10)]
+ [ValidatePattern()]
+ [string]$Computername = 'DEFAULTVALUE'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+ Get-DnsServerResourceRecord -ComputerName dc01 -ZoneName hosp.uhhg.org | where { ($_.HostName -match '^U.*XA65') -and ($_.Hostname -notmatch 'VM') -and ($_.Hostname -notmatch '.hosp.uhhg.org$') } | select @{ n = 'Hostname'; e = { $_.Hostname } }, @{n = 'IpAddres s'; e = { $_.RecordData.IPv4Address.IPAddressToString } }
+ $CitrixRecords | select -Skip 1 | % { try { Add-DnsServerResourceRecord -ZoneName hosp.uhhg.org -ComputerName dc01 -IPv4Address $_.IpAddress -Name $_.Hostname -A } catch { } }
+ $CitrixRecords | % { Get-DnsServerResourceRecord -ComputerName dc01 -Name $_.Hostname -RRType A -ZoneName hosp.uhhg.org }
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+end {
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Copy-LocalPathToVmCdRom.ps1 b/Copy-LocalPathToVmCdRom.ps1
new file mode 100644
index 0000000..cc30be5
--- /dev/null
+++ b/Copy-LocalPathToVmCdRom.ps1
@@ -0,0 +1,199 @@
+<#
+.SYNOPSIS
+ This creates an ISO file based on a specified path, uploads this ISO file to a VMware
+ datastore and mounts the ISO file on a VM.
+.NOTES
+ Created on: 7/14/2014
+ Created by: Adam Bertram
+ Filename: Copy-LocalPathToVmCdRom.ps1
+.EXAMPLE
+ .\Copy-LocalPathToVmRdRom.ps1 -Folderpath c:\folder -Vm 'VM1' -DatatoreFolder 'VM1 Folder'
+
+ This creates an ISO file from the contents of C:\folder, copies this ISO file to the
+ 'VM1 folder' datastore folder and mounts it to the VM 'VM1'
+.PARAMETER FolderPath
+ The folder path containing the files you'd like transfer to the VM
+.PARAMETER Vm
+ The name of the VMware virtual machine
+.PARAMETER Datacenter
+ The name of the Vcenter datacenter the VM is located on
+.PARAMETER Datastore
+ The name of the datastore the VM is located on
+.PARAMETER DatastoreFolder
+ The name of the datastore folder you'd like to put the ISO file into
+.PARAMETER Force
+ To remove any existing ISO on the datastore with the same name as the folder you're copying.
+ Without this parameter, if an existing ISO is found, the action will fail.
+
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateScript({Test-Path $_ -PathType 'Container'})]
+ [string]$FolderPath,
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string]$Vm,
+ [Parameter(ValueFromPipelineByPropertyName)]
+ [string]$Datacenter = 'Development',
+ [Parameter(ValueFromPipelineByPropertyName)]
+ [string]$Datastore = 'ruby02-localdatastore01',
+ [Parameter(Mandatory,
+ ValueFromPipelineByPropertyName)]
+ [string]$DatastoreFolder,
+ [switch]$Force
+)
+
+begin {
+ Set-StrictMode -Version Latest
+ try {
+ if (!(Get-PSSnapin 'VMware.VimAutomation.Core')) {
+ throw 'PowerCLI snapin is not available'
+ }
+ $VmObject = Get-VM $Vm -ErrorAction SilentlyContinue
+ if (!$VmObject) {
+ throw "VM $Vm does not exist on connected VI server"
+ }
+
+ if ($VmObject.PowerState -ne 'PoweredOn') {
+ throw "VM $Vm is not powered on. Cannot change CD-ROM IsoFilePath"
+ }
+
+ $ExistingCdRom = $VmObject | Get-CDDrive
+ if (!$ExistingCdRom.ConnectionState.Connected) {
+ throw 'No CD-ROM attached. VM is powered on so I cannot attach a new one'
+ }
+
+ $TempIsoName = "$($Folderpath | Split-Path -Leaf).iso"
+ $DatastoreIsoFolderPath = "vmstore:\$DataCenter\$Datastore\$DatastoreFolder"
+ if (Test-Path "$DatastoreIsoFolderPath\$TempIsoName") {
+ if ($Force) {
+ throw "-Force currently in progress. ISO file $DatastoreIsoFolderPath\$TempIsoName already exists in datastore"
+ ## Remove current ISO CDROM from VM
+
+ ## Remove ISO from datastore folder
+ } else {
+ throw "ISO file $DatastoreIsoFolderPath\$TempIsoName already exists in datastore"
+ }
+ }
+
+ ## Hide the PowerCLI progres bars
+ $ProgressPreference = 'SilentlyContinue'
+ function New-IsoFile {
+ <#
+ .Synopsis
+ Creates a new .iso file
+ .Description
+ The New-IsoFile cmdlet creates a new .iso file containing content from chosen folders
+ .Example
+ New-IsoFile "c:\tools","c:Downloads\utils"
+ Description
+ -----------
+ This command creates a .iso file in $env:temp folder (default location) that contains c:\tools and c:\downloads\utils folders. The folders themselves are added in the root of the .iso image.
+ .Example
+ dir c:\WinPE | New-IsoFile -Path c:\temp\WinPE.iso -BootFile etfsboot.com -Media DVDPLUSR -Title "WinPE"
+ Description
+ -----------
+ This command creates a bootable .iso file containing the content from c:\WinPE folder, but the folder itself isn't included. Boot file etfsboot.com can be found in Windows AIK. Refer to IMAPI_MEDIA_PHYSICAL_TYPE enumeration for possible media types:
+
+ http://msdn.microsoft.com/en-us/library/windows/desktop/aa366217(v=vs.85).aspx
+ .Notes
+ NAME: New-IsoFile
+ AUTHOR: Chris Wu
+ LASTEDIT: 03/06/2012 14:06:16
+ #>
+ Param (
+ [parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Source,
+ [parameter(Position = 1)][string]$Path = "$($env:temp)\" + (Get-Date).ToString("yyyyMMdd-HHmmss.ffff") + ".iso",
+ [string] $BootFile = $null,
+ [string] $Media = "Disk",
+ [string] $Title = (Get-Date).ToString("yyyyMMdd-HHmmss.ffff"),
+ [switch] $Force
+ )#End Param
+
+ Begin {
+ ($cp = new-object System.CodeDom.Compiler.CompilerParameters).CompilerOptions = "/unsafe"
+ if (!("ISOFile" -as [type])) {
+ Add-Type -CompilerParameters $cp -TypeDefinition @"
+ public class ISOFile
+ {
+ public unsafe static void Create(string Path, object Stream, int BlockSize, int TotalBlocks)
+ {
+ int bytes = 0;
+ byte[] buf = new byte[BlockSize];
+ System.IntPtr ptr = (System.IntPtr)(&bytes);
+ System.IO.FileStream o = System.IO.File.OpenWrite(Path);
+ System.Runtime.InteropServices.ComTypes.IStream i = Stream as System.Runtime.InteropServices.ComTypes.IStream;
+
+ if (o == null) { return; }
+ while (TotalBlocks-- > 0) {
+ i.Read(buf, BlockSize, ptr); o.Write(buf, 0, bytes);
+ }
+ o.Flush(); o.Close();
+ }
+ }
+"@
+ }#End If
+
+ if ($BootFile -and (Test-Path $BootFile)) {
+ ($Stream = New-Object -ComObject ADODB.Stream).Open()
+ $Stream.Type = 1 # adFileTypeBinary
+ $Stream.LoadFromFile((Get-Item $BootFile).Fullname)
+ ($Boot = New-Object -ComObject IMAPI2FS.BootOptions).AssignBootImage($Stream)
+ }#End If
+
+ $MediaType = @{
+ CDR = 2; CDRW = 3; DVDRAM = 5; DVDPLUSR = 6; DVDPLUSRW = 7; `
+ DVDPLUSR_DUALLAYER = 8; DVDDASHR = 9; DVDDASHRW = 10; DVDDASHR_DUALLAYER = 11; `
+ DISK = 12; DVDPLUSRW_DUALLAYER = 13; BDR = 18; BDRE = 19
+ }
+
+ if ($MediaType[$Media] -eq $null) { write-debug "Unsupported Media Type: $Media"; write-debug ("Choose one from: " + $MediaType.Keys); break }
+ ($Image = new-object -com IMAPI2FS.MsftFileSystemImage -Property @{ VolumeName = $Title }).ChooseImageDefaultsForMediaType($MediaType[$Media])
+
+ if ((Test-Path $Path) -and (!$Force)) { "File Exists $Path"; break }
+ New-Item -Path $Path -ItemType File -Force | Out-Null
+ if (!(Test-Path $Path)) {
+ "cannot create file $Path"
+ break
+ }
+ }
+
+ Process {
+ switch ($Source) { { $_ -is [string] } { $Image.Root.AddTree((Get-Item $_).FullName, $true) | Out-Null; continue }
+ { $_ -is [IO.FileInfo] } { $Image.Root.AddTree($_.FullName, $true); continue }
+ { $_ -is [IO.DirectoryInfo] } { $Image.Root.AddTree($_.FullName, $true); continue }
+ }#End switch
+ }#End Process
+
+ End {
+ $Result = $Image.CreateResultImage()
+ [ISOFile]::Create($Path, $Result.ImageStream, $Result.BlockSize, $Result.TotalBlocks)
+ }#End End
+ }#End function New-IsoFile
+
+
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+
+process {
+ try {
+ ## Create an ISO
+ $IsoFilePath = "$($env:TEMP)\$TempIsoName"
+ Get-ChildItem $FolderPath | New-IsoFile -Path $IsoFilePath -Title ($Folderpath | Split-Path -Leaf) -Force
+ ## Upload the ISO to the datastore
+ $Iso = Copy-DatastoreItem $IsoFilePath "vmstore:\$Datacenter\$Datastore\$DatastoreFolder" -PassThru
+ ## Attach the ISO to the VM
+ $VmObject | Get-CDDrive | Set-CDDrive -IsoPath $Iso.DatastoreFullPath -Connected $true -Confirm:$false | Out-Null
+ ## Delete the temp ISO
+ Remove-Item $IsoFilePath -Force
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Cowbell.wav b/Cowbell.wav
new file mode 100644
index 0000000..891a8de
Binary files /dev/null and b/Cowbell.wav differ
diff --git a/Create-AzureVMSnapshot.ps1 b/Create-AzureVMSnapshot.ps1
new file mode 100644
index 0000000..46028ec
--- /dev/null
+++ b/Create-AzureVMSnapshot.ps1
@@ -0,0 +1,122 @@
+<#
+.SYNOPSIS
+ This creates a blob copy (snapshot) of all disks used by an Azure VM
+.NOTES
+ Created on: 7/5/2014
+ Created by: Adam Bertram
+ Filename: Create-AzureVmSnapshot.ps1
+ Credits: http://bit.ly/1ouJKjR
+ Requirements: Azure IaaS VM
+.EXAMPLE
+ Create-AzureVmSnapshot -AzureVM CCM1 -ServiceName CLOUD
+ This example will create a blob copy of all disks in the VM CCM1, Service name CLOUD
+.EXAMPLE
+ Create-AzureVmSnapshot -AzureVM 'CCM1','CCM2' -Overwrite
+ This example creates blob copies for all disks in the VMs CCM1 and CCM2 using the default
+ service name parameter and if any existing copies are detected, automatically overwrite them.
+.PARAMETER AzureVM
+ The name of the Azure VM. Multiple VM names are supported.
+.PARAMETER ServiceName
+ The name of your Azure cloud service.
+.PARAMETER Overwrite
+ Use this switch to overwrite any existing blob copies without asking
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline)]
+ [string[]]$AzureVM,
+ [string]$ServiceName = 'ADBCLOUD',
+ [switch]$Overwrite
+)
+
+begin {
+ Set-StrictMode -Version Latest
+ try {
+ $AzureModuleFilePath = "$($env:ProgramFiles)\Microsoft SDKs\Windows Azure\PowerShell\ServiceManagement\Azure\Azure.psd1"
+ if (!(Test-Path $AzureModuleFilePath)) {
+ Write-Error 'Azure module not found'
+ } else {
+ Import-Module $AzureModuleFilePath
+ }
+
+ $script:BackupContainer = 'backups'
+
+ function New-Snapshot([Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMModel.OSVirtualHardDisk]$Disk) {
+ $Blob = $Disk.MediaLink.Segments[-1]
+ $Container = $Disk.MediaLink.Segments[-2].TrimEnd('/')
+ $BlobCopyParams = @{
+ 'SrcContainer' = $Container;
+ 'SrcBlob' = $Blob;
+ 'DestContainer' = $BackupContainer
+ }
+ if ($Overwrite.IsPresent) {
+ $BlobCopyParams.Force = $true
+ }
+ Start-AzureStorageBlobCopy @BlobCopyParams
+ #Get-AzureStorageBlobCopyState -Container $BackupContainer -Blob $Blob -WaitForComplete
+ }
+
+ ## Ensure the container is created to store the snapshot
+ if (!(Get-AzureStorageContainer -Name $BackupContainer -ea SilentlyContinue)) {
+ Write-Verbose "Container $BackupContainer not found. Creating..."
+ New-AzureStorageContainer -Name $BackupContainer -Permission Off
+ }
+
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+
+process {
+ try {
+ foreach ($Vm in $AzureVM) {
+ $Vm = Get-AzureVM -ServiceName $ServiceName -Name $Vm
+ if ($Vm.Status -ne 'StoppedVM') {
+ if ($Vm.Status -eq 'ReadyRole') {
+ Write-Verbose "VM $($Vm.Name) is started. Bringing down into a provisioned state"
+ ## Bring the VM down in a provisioned state
+ $Vm | Stop-AzureVm -StayProvisioned
+ } elseif ($Vm.Status -eq 'StoppedDeallocated') {
+ Write-Verbose "VM $($Vm.Name) is stopped but not in a provisioned state."
+ ## Bring up the VM and bring it back down in a provisioned state
+ Write-Verbose "Starting up VM $($Vm.Name)..."
+ $Vm | Start-AzureVm
+ while ((Get-AzureVm -ServiceName $ServiceName -Name $Vm.Name).Status -ne 'ReadyRole') {
+ sleep 5
+ Write-Verbose "Waiting on VM $($Vm.Name) to be in a ReadyRole state..."
+ }
+ Write-Verbose "VM $($Vm.Name) now up. Bringing down into a provisioned state..."
+ $Vm | Stop-AzureVm -StayProvisioned
+ }
+
+ }
+
+ $OsDisk = $Vm | Get-AzureOSDisk
+ Get-AzureSubscription | Set-AzureSubscription -CurrentStorageAccountName ($OsDisk.MediaLink.Host.Split('.')[0])
+
+ ## Take snapshot of OS disk
+ New-Snapshot -Disk $OsDisk
+
+ ## Take snapshots of all data disks
+ $DataDisks = $Vm | Get-AzureDataDisk
+ if ($DataDisks) {
+ foreach ($DataDisk in $DataDisks) {
+ New-Snapshot -Disk $DataDisk
+ }
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+
+end {
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Create-CMRandomMemberCollection.ps1 b/Create-CMRandomMemberCollection.ps1
new file mode 100644
index 0000000..c332b1a
--- /dev/null
+++ b/Create-CMRandomMemberCollection.ps1
@@ -0,0 +1,126 @@
+<#
+.SYNOPSIS
+ This script adds a random sampling of online clients from another ConfigMgr collection
+.DESCRIPTION
+ This script is useful for creating test collections for testing software deployments. It pings a random sampling of clients from
+ one collection and adds them into another collection.
+.EXAMPLE
+ .\CreateRandomMemberConfigMgrCollection.ps1 -Server CONFIGMANAGER -SourceCollectionName COLLECTIONNAME -DestinationCollectionName COLLECTIONNAME -NumberOfClients 30
+.PARAMETER Server
+ The ConfigMgr site server name
+.PARAMETER SourceCollectionName
+ The name of the ConfigMgr collection that will be used to sample clients from
+.PARAMETER DestinationCollectionName
+ The name of the ConfigMgr collection that will be used (or created if doesn't exist) to place the random sampling of clients
+.PARAMETER NumberOfClients
+ This is how many clients are taken at random from the source collection to make the destination collection's members
+.NOTES
+ Revisions: 06/11/2014
+ - filtered $SourceClients by only including OSes 'Microsoft Windows NT'
+ - modified the way a random client is chosen
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False,
+ HelpMessage = 'What site server would you like to connect to?')]
+ [string]$Server = 'CONFIGMANAGER',
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False,
+ HelpMessage = 'What site does your ConfigMgr site server exist in?')]
+ [string]$Site = 'UHP',
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $True,
+ ValueFromPipelineByPropertyName = $True,
+ HelpMessage = 'What device collection would you like to get clients from?')]
+ [string]$SourceCollectionName,
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $True,
+ ValueFromPipelineByPropertyName = $True,
+ HelpMessage = 'What device collection would you like to put the random clients into?')]
+ [string]$DestinationCollectionName,
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $True,
+ HelpMessage = 'How many random clients would you like to put into the destination collection?')]
+ [int]$NumberOfClients
+)
+
+begin {
+ try {
+ ## I capture the drive path first because I have to switch to the ConfigMgr provider drive
+ ## to use the ConfigMgr cmdlets. I will change the drive path to the original after the
+ ## script is complete.
+ $BeforeLocation = (Get-Location).Path
+ Set-Location "$Site`:"
+
+ $ConfigMgrWmiProps = @{
+ 'ComputerName' = $Server;
+ 'Namespace' = "root\sms\site_$Site"
+ }
+
+ Write-Verbose 'Verifying parameters...'
+ if (!(Get-CmDeviceCollection -Name $SourceCollectionName)) {
+ throw "$SourceCollectionName does not exist"
+ } elseif (!(Get-CmDeviceCollection -Name $DestinationCollectionName)) {
+ throw "$DestinationCollectionName does not exist"
+ }
+ $SourceCollectionId = (Get-WmiObject @ConfigMgrWmiProps -Class SMS_Collection -Filter "Name = '$SourceCollectionName'").CollectionId
+ $DestinationCollectionId = (Get-WmiObject @ConfigMgrWmiProps -Class SMS_Collection -Filter "Name = '$DestinationCollectionName'").CollectionId
+ $SourceClients = Get-WmiObject @ConfigMgrWmiProps -Class "SMS_CM_RES_COLL_$SourceCollectionId" | where { $_.DeviceOS -match 'Microsoft Windows NT' } | select Name, DeviceOS | group DeviceOS | sort count -Descending
+ $ExistingDestinationClients = Get-WmiObject @ConfigMgrWmiProps -Class "SMS_CM_RES_COLL_$DestinationCollectionId" | select -ExpandProperty Name
+ if ($SourceClients.Count -eq 0) {
+ throw 'Source collection does not contain any members'
+ }
+ $TargetClients = @()
+ } catch {
+ ## TODO: Ensure this breaks out of the entire script rather than just the BEGIN block
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+ if ($NumberOfClients -lt $SourceClients.Count) {
+ Write-Verbose "Number of clients needed ($NumberOfClients) are less than total number of operating system groups ($($SourceClients.Count))..."
+ ## Find the OSes that have the highest count and get random clients from each of those
+ ## $SourceClients was sorted desc above so I know the lowest array indexes have the highest counts
+ $OsGroups = $SourceClients[0..($NumberOfClients - 1)]
+ } else {
+ Write-Verbose "Number of clients needed ($NumberOfClients) are equal to or exceed total operating system groups ($($SourceClients.Count))..."
+ $OsGroups = $SourceClients
+ }
+ Write-Verbose "Total OS groupings: $($OsGroups.Count)"
+
+ ## TODO: What if the number of clients needed is greater than the online source clients?
+ for ($i = 0; $TargetClients.Count -lt $NumberOfClients; $i++) {
+ $GroupIndex = $i % $OsGroups.Count
+ Write-Verbose "Using group index $GroupIndex..."
+ $OsGroup = $OsGroups[$GroupIndex].Group
+ Write-Verbose "Using group $($OsGroups[$GroupIndex].Name)..."
+ $ClientName = ($OsGroups[$GroupIndex].Group | Get-Random).Name
+ Write-Verbose "Testing $ClientName for validity to add to target collection..."
+ if (($TargetClients -notcontains $ClientName) -and ($ExistingDestinationClients -notcontains $ClientName) -and (Test-Ping $ClientName)) {
+ Write-Verbose "$ClientName found to be acceptable. Adding to target collection array..."
+ $TargetClients += $ClientName
+ }
+ }
+ Write-Verbose 'Finished checking clients. Begin adding to target collection...'
+
+ ## The TargetClients array is necessary instead of directly adding membership rules in the for loop
+ ## before if not, I couldn't ensure dups aren't added
+ $TargetClients |
+ foreach {
+ Write-Verbose "Adding $($_) to target collection $DestinationCollectionName..."
+ Add-CMDeviceCollectionDirectMembershipRule -CollectionName $DestinationCollectionName -ResourceId (Get-CmDevice -Name $_).ResourceID
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+end {
+ Set-Location $BeforeLocation
+}
\ No newline at end of file
diff --git a/DownloadQualysPatches.ps1 b/DownloadQualysPatches.ps1
new file mode 100644
index 0000000..f9cc5d8
--- /dev/null
+++ b/DownloadQualysPatches.ps1
@@ -0,0 +1,17 @@
+param($scan_results_file)
+$download_path = 'C:\Documents and Settings\abertram\Desktop\CONFIGMANAGER Vulnerabilities'
+
+$mht_contents = gc $scan_results_file
+$ie = New-Object -ComObject 'InternetExplorer.Application'
+$webclient = New-Object System.Net.WebClient
+$ie.Navigate2('file:///C:\Documents and Settings\abertram\Desktop\CONFIGMANAGER Vulnerabilities\Scan_Results_unnhs_tw1_20131121_scan_1385046170_57428.mht')
+
+$links = $ie.Document.getElementsByTagName('a') | ? {($_.innertext -like '*Windows Server 2012*') -and ($_.innertext -notlike '*Core*') -and ($_.href -like 'http://*')} | select -ExpandProperty href
+foreach ($link in $links) {
+ $r = Invoke-WebRequest -Uri $link -UseBasicParsing
+ $r.Links | ? {$_.outerhtml -like '*Download*'} | % {
+ $_.href
+ $y = Invoke-WebRequest "http://www.microsoft.com$($_.href)" -UseBasicParsing
+ }
+ $y.Links | ? {$_.outerhtml -like '*Click Here*'} | select -first 1 | % {$webclient.DownloadFile($_.href,"$download_path\$(split-path $_.href -Leaf)")}
+}
diff --git a/Enable-RemotePSRemoting.ps1 b/Enable-RemotePSRemoting.ps1
new file mode 100644
index 0000000..ff08731
--- /dev/null
+++ b/Enable-RemotePSRemoting.ps1
@@ -0,0 +1,78 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 5/9/2014 3:46 PM
+ Created by: Adam Bertram
+ Organization:
+ Filename:
+.DESCRIPTION
+
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER Computername
+ The remote computer to enable PS remoting on
+
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $True,
+ ValueFromPipelineByPropertyName = $True)]
+ [string]$Computername,
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [string]$PsExecPath = 'C:\PsExec.exe'
+)
+
+begin {
+ ## http://www.leeholmes.com/blog/2009/11/20/testing-for-powershell-remoting-test-psremoting/
+ function Test-PsRemoting {
+ param (
+ [Parameter(Mandatory = $true)]
+ $computername
+ )
+
+ try {
+ $errorActionPreference = "Stop"
+ $result = Invoke-Command -ComputerName $computername { 1 }
+ } catch {
+ return $false
+ }
+
+ ## I’ve never seen this happen, but if you want to be
+ ## thorough….
+ if ($result -ne 1) {
+ Write-Verbose "Remoting to $computerName returned an unexpected result."
+ return $false
+ }
+ $true
+ }
+
+ if (!(Test-Ping $Computername)) {
+ throw 'Computer is not reachable'
+ } elseif (!(Test-Path $PsExecPath)) {
+ throw 'Psexec.exe not found'
+ }
+}
+
+process {
+ if (Test-PsRemoting $Computername) {
+ Write-Warning "Remoting already enabled on $Computername"
+ } else {
+ Write-Verbose "Attempting to enable remoting on $Computername..."
+ & $PsExecPath "\\$Computername" -s c:\windows\system32\winrm.cmd quickconfig -quiet
+ if (!(Test-PsRemoting $Computername)) {
+ Write-Warning "Remoting was attempted but not enabled on $Computername"
+ } else {
+ Write-Verbose "Remoting successfully enabled on $Computername"
+ }
+ }
+}
+
+end {
+
+}
\ No newline at end of file
diff --git a/Find-WmiClass.ps1 b/Find-WmiClass.ps1
new file mode 100644
index 0000000..4a5ed73
--- /dev/null
+++ b/Find-WmiClass.ps1
@@ -0,0 +1,98 @@
+<#
+.SYNOPSIS
+ This script finds a WMI class by name in all namespaces on a compputer
+.NOTES
+ Created on: 1/29/15
+ Created by: Adam Bertram
+ Filename: Find-WmiClass.ps1
+.EXAMPLE
+ PS> Find-WmiClas.ps1 -ClassSearchString 'MatchThis' -Computername 'MYSERVER'
+
+ This example will recursively query all WMI namespaces on the computer MYSERVER and attempt to find any
+ CIM class that is the exact value of 'MatchThis'
+.EXAMPLE
+ PS> Find-WmiClas.ps1 -Computername 'MYSERVER'
+
+ This example will recursively query all WMI namespaces on the computer MYSERVER and return all WMI classes
+.EXAMPLE
+ PS> Find-WmiClas.ps1 -ClassSearchString '*Wildcard*' -Computername 'MYSERVER'
+
+ This example will recursively query all WMI namespaces on the computer MYSERVER and and attempt to find any
+ CIM class that matches the value of '*WildCard*'
+.PARAMETER PARAM1
+
+.PARAMETER PARAM2
+
+#>
+[CmdletBinding()]
+param (
+ [string]$ClassSearchString = '*',
+ [string]$PropertySearchString = '*',
+ [string]$Namespace = 'root',
+ [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
+ [string]$Computername = 'localhost'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ function Get-Namespace ($Namespace = 'root',[switch]$Recursive) {
+ try {
+ $Ns = Get-WmiObject -Computername $Computername -Namespace $Namespace -Class '__Namespace' -ea 'SilentlyContinue' | Select-Object -ExpandProperty Name
+ if (!$Ns) {
+ $false
+ } else {
+ foreach ($n in $Ns) {
+ try {
+ $Ns = "$Namespace\$n"
+ if ($Recursive.IsPresent) {
+ Get-Namespace -Namespace $Ns -Recursive
+ } else {
+ $Ns
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ $false
+ }
+ }
+}
+
+process {
+ $Namespaces = @()
+ $Namespaces += Get-Namespace -Namespace $Namespace -Recursive;
+ if ($Namespaces) {
+ $Namespaces += $Namespace
+ foreach ($Ns in $Namespaces) {
+ try {
+ $Classes = Get-CimClass -Computername $Computername -Namespace $Ns -ClassName $ClassSearchString -ea SilentlyContinue
+ if ($Classes) {
+ foreach ($Class in $Classes) {
+ if ($PropertySearchString -eq '*') {
+ [pscustomobject]@{ 'Computername' = $Computername; 'Namespace' = $Ns; 'Class' = $Class.CimClassName }
+ } else {
+ $Properties = $Class.CimClassProperties
+ if ($Properties.Count -gt 0) {
+ foreach ($Prop in ($Properties.Name | where { $_ -like $PropertySearchString })) {
+ try {
+ $Value = Get-Wmiobject -Computername $Computername -Namespace $Ns -Class $Class.CimClassName -ea silentlycontinue | select -ExpandProperty $Prop
+ [pscustomobject]@{ 'Computername' = $Computername; 'Namespace' = $Ns; 'Class' = $Class.CimClassName; 'Property' = $Prop; 'Value' = $Value }
+ } catch {
+
+ }
+ }
+ }
+ [pscustomobject]@{ 'Computername' = $Computername; 'Namespace' = $Ns; 'Class' = $Class.CimClassName; }
+ }
+ }
+ }
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Get-AdDnsRecordAcl.ps1 b/Get-AdDnsRecordAcl.ps1
new file mode 100644
index 0000000..04e5a35
--- /dev/null
+++ b/Get-AdDnsRecordAcl.ps1
@@ -0,0 +1,60 @@
+#Requires -Module ActiveDirectory
+
+<#
+.SYNOPSIS
+ This script retrieves the ACL from an Active Directory-integrated DNS record
+.NOTES
+ Created on: 8/5/2014
+ Created by: Adam Bertram
+ Filename: Get-AdDnsRecordAcl.ps1
+.EXAMPLE
+ PS> .\Get-AdDnsRecordAcl.ps1 -Hostname 'SERVER1'
+
+ This example retrieves the ACL for the hostname SERVER1 inside the current forest-integrated
+ DNS zone inside Active Directory
+.EXAMPLE
+ PS> .\Get-AdDnsRecordAcl.ps1 -Hostname 'SERVER1' -AdDnsIntegration 'Domain'
+
+ This example retrieves the ACL for the hostname SERVER1 inside the current domain-integrated
+ DNS zone inside Active Directory
+.PARAMETER Hostname
+ The hostname for the DNS record you'd like to see
+.PARAMETER DomainName
+ The Active Directory domain name. This defaults to the current domain
+.PARAMETER AdDnsIntegration
+ This is the DNS integration type. This can either be Forest and Domain.
+
+#>
+[CmdletBinding()]
+[OutputType('System.DirectoryServices.ActiveDirectorySecurity')]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string[]]$Hostname,
+ [Parameter(ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string]$DomainName = (Get-ADDomain).Forest,
+ [ValidateSet('Forest','Domain')]
+ [Parameter(ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string[]]$AdDnsIntegration = 'Forest'
+)
+
+begin {
+ $ErrorActionPreference = 'Stop'
+ Set-StrictMode -Version Latest
+}
+
+process {
+ try {
+ $Path = "AD:\DC=$DomainName,CN=MicrosoftDNS,DC=$AdDnsIntegration`DnsZones,DC=$($DomainName.Split('.') -join ',DC=')"
+ foreach ($Record in (Get-ChildItem -Path $Path)) {
+ if ($Hostname -contains $Record.Name) {
+ Get-Acl -Path "ActiveDirectory:://RootDSE/$($Record.DistinguishedName)"
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Get-AdUserMatches.ps1 b/Get-AdUserMatches.ps1
new file mode 100644
index 0000000..d3b8243
--- /dev/null
+++ b/Get-AdUserMatches.ps1
@@ -0,0 +1,373 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename:
+ Credits:
+ Requirements:
+ Todos:
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER PARAM1
+
+.PARAMETER PARAM2
+
+#>
+[CmdletBinding(DefaultParameterSetName = 'CSV')]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [hashtable]$AdToSourceFieldMappings = @{ 'givenName' = 'FirstName'; 'Initials' = 'MiddleInitial'; 'surName' = 'LastName' },
+ [hashtable]$AdToOutputFieldMappings = @{ 'givenName' = 'AD First Name'; 'Initials' = 'AD Middle Initial'; 'surName' = 'AD Last Name'; 'samAccountName' = 'AD Username' },
+ [ValidateScript({Test-Path -Path $_ -PathType 'Leaf'})]
+ [Parameter(Mandatory, ParameterSetName = 'CSV')]
+ [ValidateScript({Test-Path -Path $_ -PathType 'Leaf' })]
+ [string]$CsvFilePath
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ #region MatchTests
+ function Test-MatchFirstNameLastName ($FirstName, $LastName) {
+ if ($FirstName -and $LastName) {
+ Write-Verbose -Message "$($MyInvocation.MyCommand) - Searching for AD user match based on first name '$FirstName', last name '$LastName'"
+ $Match = $AdUsers | where { ($_.givenName -eq $FirstName) -and ($_.surName -eq $LastName) }
+ if ($Match) {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match(es) found!"
+ $Match
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found"
+ $false
+ }
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found. Either first or last name is null"
+ $false
+ }
+ }
+
+ function Test-MatchFirstNameMiddleInitialLastName ($FirstName, $MiddleInitial, $LastName) {
+ if ($FirstName -and $LastName -and $MiddleInitial) {
+ Write-Verbose -Message "$($MyInvocation.MyCommand) - Searching for AD user match based on first name '$FirstName', middle initial '$MiddleInitial' and last name '$LastName'"
+ $Match = $AdUsers | where { ($_.givenName -eq $FirstName) -and ($_.surName -eq $LastName) -and (($_.Initials -eq $MiddleInitial) -or ($_.Initials -eq "$MiddleInitial.")) }
+ if ($Match) {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match(es) found!"
+ $Match
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found"
+ $false
+ }
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found. Either first name, middle initial or last name is null"
+ $false
+ }
+ }
+
+ function Test-MatchFirstInitialLastName ($FirstName,$LastName) {
+ Write-Verbose -Message "$($MyInvocation.MyCommand) - Searching for AD user match based on first initial '$($FirstName.Substring(0,1))' and last name '$LastName'"
+ if ($FirstName -and $LastName) {
+ $Match = $AdUsers | where { "$($FirstName.SubString(0, 1))$LastName" -eq $_.samAccountName }
+ if ($Match) {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match(es) found!"
+ $Match
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found"
+ $false
+ }
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found. Either first name or last name is null"
+ $false
+ }
+ }
+ <#
+ function Test-MatchLikeFirstNameLikeLastName ($FirstName, $LastName) {
+ if ($FirstName -and $LastName) {
+ Write-Verbose -Message "$($MyInvocation.MyCommand) - Searching for AD user match based on like first name '$FirstName', last name '$LastName'"
+ $Match = $AdUsers | where { ($FirstName -like "*$($_.givenName)*") -and ($LastName -like "*$($_.surName)*") }
+ if ($Match) {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match(es) found!"
+ $Match
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found"
+ $false
+ }
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found. Either first or last name is null"
+ $false
+ }
+ }
+ #>
+ <#
+ function Test-MatchCommonFirstNameTranslationsLastName ($FirstName, $LastName) {
+ $Translations = @{
+ 'Kathy' = 'Kathleen'
+ 'Randy' = 'Randall'
+ 'Bob' = 'Robert'
+ 'Rob' = 'Robert'
+ }
+ if ($FirstName -and $LastName) {
+ Write-Verbose -Message "$($MyInvocation.MyCommand) - Searching for AD user match based on first name '$FirstName', last name '$LastName'"
+ $Match = $AdUsers | where { ($FirstName -match $_.givenName) -and ($LastName -match $_.surName) }
+ if ($Match) {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match(es) found!"
+ $Match
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found"
+ $false
+ }
+ } else {
+ Write-Verbose "$($MyInvocation.MyCommand) - Match not found. Either first or last name is null"
+ $false
+ }
+ }
+ #>
+
+ #function Test-MatchLevenshteinDistance {
+ #
+ #}
+ #endregion
+
+ #region ValidationTests
+ function Test-CsvField {
+ $CsvHeaders = (Get-Content $CsvFilePath | Select-Object -First 1).Split(',').Trim('"')
+ $AdToSourceFieldMappings.Values | foreach {
+ if (!($CsvHeaders -like $_)) {
+ return $false
+ }
+ }
+ $true
+ }
+ #endregion
+
+ #region Functions
+ function Get-LevenshteinDistance {
+ # get-ld.ps1 (Levenshtein Distance)
+ # Levenshtein Distance is the # of edits it takes to get from 1 string to another
+ # This is one way of measuring the "similarity" of 2 strings
+ # Many useful purposes that can help in determining if 2 strings are similar possibly
+ # with different punctuation or misspellings/typos.
+ #
+ ########################################################
+
+ # Putting this as first non comment or empty line declares the parameters
+ # the script accepts
+ ###########
+ param ([string] $first, [string] $second, [switch] $ignoreCase)
+
+ # No NULL check needed, why is that?
+ # PowerShell parameter handling converts Nulls into empty strings
+ # so we will never get a NULL string but we may get empty strings(length = 0)
+ #########################
+
+ $len1 = $first.length
+ $len2 = $second.length
+
+ # If either string has length of zero, the # of edits/distance between them
+ # is simply the length of the other string
+ #######################################
+ if ($len1 -eq 0) { return $len2 }
+
+ if ($len2 -eq 0) { return $len1 }
+
+ # make everything lowercase if ignoreCase flag is set
+ if ($ignoreCase -eq $true) {
+ $first = $first.tolowerinvariant()
+ $second = $second.tolowerinvariant()
+ }
+
+ # create 2d Array to store the "distances"
+ $dist = new-object -type 'int[,]' -arg ($len1 + 1), ($len2 + 1)
+
+ # initialize the first row and first column which represent the 2
+ # strings we're comparing
+ for ($i = 0; $i -le $len1; $i++) { $dist[$i, 0] = $i }
+ for ($j = 0; $j -le $len2; $j++) { $dist[0, $j] = $j }
+
+ $cost = 0
+
+ for ($i = 1; $i -le $len1; $i++) {
+ for ($j = 1; $j -le $len2; $j++) {
+ if ($second[$j - 1] -ceq $first[$i - 1]) {
+ $cost = 0
+ } else {
+ $cost = 1
+ }
+
+ # The value going into the cell is the min of 3 possibilities:
+ # 1. The cell immediately above plus 1
+ # 2. The cell immediately to the left plus 1
+ # 3. The cell diagonally above and to the left plus the 'cost'
+ ##############
+ # I had to add lots of parentheses to "help" the Powershell parser
+ # And I separated out the tempmin variable for readability
+ $tempmin = [System.Math]::Min(([int]$dist[($i - 1), $j] + 1), ([int]$dist[$i, ($j - 1)] + 1))
+ $dist[$i, $j] = [System.Math]::Min($tempmin, ([int]$dist[($i - 1), ($j - 1)] + $cost))
+ }
+ }
+
+ # the actual distance is stored in the bottom right cell
+ return $dist[$len1, $len2];
+ }
+
+ <#
+ function Test-DataRow ([object]$SourceRowData) {
+ ## Check for instances where all fields are null
+ $FieldCount = $SourceRowData.psObject.Properties.Name.Count
+ $NullValues = $SourceRowData.psObject.Properties.Value | where { $_ -eq $null }
+ if ($NullValues -and ($FieldCount -eq $NullValues.Count)) {
+ Write-Warning 'This source data row contains all null fields'
+ $false
+ } else {
+ $true
+ }
+ }
+ #>
+
+ function New-OutputRow ([object]$SourceRowData) {
+ $OutputRow = [ordered]@{
+ 'Match' = $false;
+ 'MatchTest' = 'N/A'
+ }
+ $AdToOutputFieldMappings.Values | foreach {
+ $OutputRow[$_] = 'N/A'
+ }
+
+ $SourceRowData.psobject.Properties | foreach {
+ if ($_.Value) {
+ $OutputRow[$_.Name] = $_.Value
+ }
+ }
+ $OutputRow
+ }
+
+ function Add-ToOutputRow ([hashtable]$OutputRow, [object]$AdRowData, $MatchTest) {
+ $AdToOutputFieldMappings.Keys | foreach {
+ if ($AdRowData.$_) {
+ $OutputRow[$AdToOutputFieldMappings[$_]] = $AdRowData.$_
+ }
+ $OutputRow.MatchTest = $MatchTest
+ }
+ $OutputRow
+ }
+
+ function Test-TestMatchValid ($FunctionParameters) {
+ $Compare = Compare-Object -ReferenceObject $FunctionParameters -DifferenceObject ($AdToSourceFieldMappings.Values | % { $_ }) -IncludeEqual -ExcludeDifferent
+ if (!$Compare) {
+ $false
+ } elseif ($Compare.Count -ne $FunctionParameters.Count) {
+ $false
+ } else {
+ $true
+ }
+ }
+
+ function Get-FunctionParams ($Function) {
+ $Function.Parameters.Keys | where { $AdToSourceFieldMappings.Values -contains $_ }
+ }
+ #endregion
+
+ ## Each row of the reference data source will be checked for a match with an AD user object by
+ ## attempting a match based on any of the Test-Match* functions. Each match function needs to be
+ ## assigned a priority. A match will be attempted starting from the highest priority match function
+ ## to the lowest until a match is made. Once the match is made, matching will stop.
+ $MatchFunctionPriorities = @{
+ 'Test-MatchFirstNameMiddleInitialLastName' = 1
+ 'Test-MatchFirstNameLastName' = 2
+ #'Test-MatchLikeFirstNameLikeLastName' = 3
+ 'Test-MatchFirstInitialLastName' = 4
+ }
+
+ if ($PSBoundParameters.CsvFilePath) {
+ Write-Verbose -Message "Verifying all field names in the $CsvFilePath match $($AdToSourceFieldMappings.Values -join ',')"
+ if (!(Test-CsvField)) {
+ throw "One or more fields specified in the `$AdToSourceFieldMappings param do not exist in the CSV file $CsvFilePath"
+ } else {
+ Write-Verbose "The CSV file's field match source field mappings"
+ }
+ }
+
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ return
+ }
+
+ Write-Verbose -Message "Retrieving all Active Directory user objects..."
+ $script:AdUsers = Get-ADUser -Filter * -Properties 'DisplayName','Initials'
+}
+
+process {
+ try {
+ ## Find all functions in memory that match Test-match*. This will automatically include all of our tests
+ ## without having to call them one at a time. This will also automatically match all functions with only
+ ## the fields the source data has
+ $TestFunctions = Get-ChildItem function:\Test-Match* | where { !$_.Module }
+ Write-Verbose "Found $($TestFunctions.Count) test functions in the script"
+ $MatchTestsToRun = @()
+ foreach ($TestFunction in $TestFunctions) {
+ Write-Verbose -Message "Checking to see if we'll use the $($TestFunction.Name) function"
+ if (Test-TestMatchValid -FunctionParameters ($TestFunction.Parameters.Keys | % { $_ })) {
+ Write-Verbose -Message "The source data has all of the function $($TestFunction.Name)'s parameters. We'll try this one"
+ $MatchTestsToRun += [System.Management.Automation.FunctionInfo]$TestFunction
+ } else {
+ Write-Verbose -Message "The parameters $($AdToSourceFieldMappings.Keys -join ',') are not adequate for the function $($TestFunction.Name)"
+ }
+ }
+
+ ## Once all the tests have been gathered that apply to the fields in the data source match them with the
+ ## function name in the priorities hash table and sort them by priority
+ $MatchTestsToRun | foreach {
+ $Test = $_;
+ foreach ($i in $MatchFunctionPriorities.GetEnumerator()) {
+ if ($Test.Name -eq $i.Key) {
+ Write-Verbose "Assigning a priority of $($i.Value) to function $($Test.Name)"
+ $Test | Add-Member -NotePropertyName 'Priority' -NotePropertyValue $i.Value
+ }
+ }
+ }
+ $MatchTestsToRun = $MatchTestsToRun | Sort-Object Priority
+
+ if ($CsvFilePath) {
+ $DataRows = Import-Csv -Path $CsvFilePath
+ }
+
+ ## Add any future data sources here ensuring they all get assigned to $DataRows
+
+ foreach ($Row in $DataRows) {
+ #if (Test-DataRow -SourceRowData $Row) {
+ [hashtable]$OutputRow = New-OutputRow -SourceRowData $Row
+ ## Run the match tests that only apply to the fields in the source data
+ ## This prevents tests from being run that have param requirements that our
+ ## source data doesn't have like when the source data only has first name and
+ ## last name. This means we couldn't run the Test-FirstNameMiddleInitialLastName function.
+ foreach ($Test in $MatchTestsToRun) {
+ Write-Verbose -Message "Running function $($Test.Name)..."
+ [array]$FuncParamKeys = Get-FunctionParams -Function $Test
+ [hashtable]$FuncParams = @{ }
+ [array]$FuncParamKeys | foreach {
+ $Row.psObject.Properties | foreach {
+ if ([array]$FuncParamKeys -contains [string]$_.Name) {
+ $FuncParams[$_.Name] = $_.Value
+ }
+ }
+ }
+ Write-Verbose -Message "Passing the parameters '$($FuncParams.Keys -join ',')' with values '$($FuncParams.Values -join ',')' to the function $($Test.Name)"
+ $AdTestResultObject = & $Test @FuncParams
+ if ($AdTestResultObject) {
+ $OutputRow.Match = $true
+ foreach ($i in $AdTestResultObject) {
+ $OutputRow = Add-ToOutputRow -AdRowData $i -OutputRow $OutputRow -MatchTest $Test.Name
+ }
+ break
+ }
+ }
+ #}
+ [pscustomobject]$OutputRow
+ }
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+}
\ No newline at end of file
diff --git a/Get-CMDpContent.ps1 b/Get-CMDpContent.ps1
new file mode 100644
index 0000000..565fb49
--- /dev/null
+++ b/Get-CMDpContent.ps1
@@ -0,0 +1,145 @@
+<#
+.Synopsis
+ Gets the Objects you specify distributed to a Distribution Point
+.DESCRIPTION
+ The Function queries the SMS_DistributionPoint to get a specific type of Objects being stored on a DistributionPoint.
+ Then it processes each Object to get the Name of the Application, package etc being stored on the DP.
+ Note for the Application Objects we use ModelName to filter instead of the PackageID as for SMS_Application WMI Class the property PackageID is lazy property
+.EXAMPLE
+ get-DPContent -DPname dexsccm -ObjectType Application
+
+ DP ObjectType Application
+ -- ---------- -----------
+ dexsccm Application 7-Zip
+ dexsccm Application PowerShell Community Extensions
+ dexsccm Application {Quest Active Roles Managment Shell for AD, QuestAD}
+ dexsccm Application PowerShell Set PageFile
+ dexsccm Application {NotePad++, NotePad++, NotePad++, NotePad++...}
+
+ Invoke the Function and ask only about the Applications distributed to the DP
+.EXAMPLE
+ Get-DPContent -DPname dexsccm
+
+ DP ObjectType Package PackageID
+ -- ---------- ------- ---------
+ dexsccm Package User State Migration Tool for Windows 8 DEX00001
+ dexsccm Package Configuration Manager Client Package DEX00002
+
+ Invoke the Funtion and only ask for the Packages distributed to the DP
+.LINK
+ http://dexterposh.blogspot.com/2014/07/powershell-sccm-2012-get-dpcontent.html
+.NOTES
+ Author - DexterPOSH
+ Inspired by - Adam Bertram's tweet ;)
+#>
+function Get-DPContent {
+ [CmdletBinding()]
+ [OutputType([PSObject[]])]
+ Param
+ (
+ # Specify the Distribution Point Name
+ [Parameter(Mandatory = $true,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string[]]$DPname,
+
+ #specfiy the ObjectType to get. Default is "Package"
+ [Parameter()]
+ [ValidateSet("Package", "Application", "ImagePackage", "BootImagePackage", "DriverPackage", "SoftwareUpdatePackage")]
+ [string]$ObjectType = "Package",
+
+ #specify the SCCMServer having SMS Namespace provider installed for the site. Default is the local machine.
+ [Parameter(Mandatory = $false)]
+ [Alias("SMSProvider")]
+ [String]$SCCMServer = "dexsccm"
+ )
+
+ begin {
+ Write-Verbose -Message "[BEGIN] Starting Function"
+ #region open a CIM session
+ $CIMSessionParams = @{
+ ComputerName = $SCCMServer
+ ErrorAction = 'Stop'
+ }
+ try {
+ if ((Test-WSMan -ComputerName $SCCMServer -ErrorAction SilentlyContinue).ProductVersion -match 'Stack: 3.0') {
+ Write-Verbose -Message "[BEGIN] WSMAN is responsive"
+ $CimSession = New-CimSession @CIMSessionParams
+ $CimProtocol = $CimSession.protocol
+ Write-Verbose -Message "[BEGIN] [$CimProtocol] CIM SESSION - Opened"
+ } else {
+ Write-Verbose -Message "[PROCESS] Attempting to connect with protocol: DCOM"
+ $CIMSessionParams.SessionOption = New-CimSessionOption -Protocol Dcom
+ $CimSession = New-CimSession @CIMSessionParams
+ $CimProtocol = $CimSession.protocol
+
+ Write-Verbose -Message "[BEGIN] [$CimProtocol] CIM SESSION - Opened"
+ }
+
+ #endregion open a CIM session
+
+ $sccmProvider = Get-CimInstance -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -Namespace "root\sms" -CimSession $CimSession -ErrorAction Stop
+ # Split up the namespace path
+ $Splits = $sccmProvider.NamespacePath -split "\\", 4
+ Write-Verbose "[BEGIN] Provider is located on $($sccmProvider.Machine) in namespace $($splits[3])"
+
+ # Create a new hash to be passed on later
+ $hash = @{ "CimSession" = $CimSession; "NameSpace" = $Splits[3]; "ErrorAction" = "Stop" }
+
+ switch -exact ($ObjectType) {
+ 'Package' { $ObjectTypeID = 2; $ObjectClass = "SMS_Package"; break }
+ 'Application' { $ObjectTypeID = 31; $ObjectClass = "SMS_Application"; break }
+ 'ImagePackage' { $ObjectTypeID = 18; $ObjectClass = "SMS_ImagePackage"; break }
+ 'BootImagePackage' { $ObjectTypeID = 19; $ObjectClass = "SMS_BootImagePackage"; break }
+ 'DriverPackage' { $ObjectTypeID = 23; $ObjectClass = "SMS_DriverPackage"; break }
+ 'SoftwareUpdatePackage' { $ObjectTypeID = 24; $ObjectClass = "SMS_SoftwareUpdatesPackage"; break }
+ }
+ } catch {
+ Write-Warning "[BEGIN] $SCCMServer needs to have SMS Namespace Installed"
+ throw $Error[0].Exception
+ }
+ }
+
+ process {
+ foreach ($DP in $DPname) {
+ Write-Verbose -Message "[PROCESS] Working with Distribution Point $DP"
+ try {
+ if ($ObjectType -eq "Application") {
+ #for the SMS_Application Objects the PackagedID is a Lazy property so have to filter on the ModelName property
+ $SecureObjectIDs = Get-CimInstance -query "Select SecureObjectID from SMS_DistributionPoint Where (ServerNALPath LIKE '%$DP%') AND (ObjectTypeID='$ObjectTypeID')" @hash | select -ExpandProperty SecureObjectId
+
+ $SecureObjectIDs | foreach {
+ if ($App = Get-CimInstance -query "Select LocalizedDisplayName,LocalizedDescription from $ObjectClass WHERE ModelName='$_'" @hash | select -Unique) {
+ [pscustomobject]@{
+ DP = $DP
+ ObjectType = $ObjectType
+ Application = $App.LocalizedDisplayName
+ Description = $app.LocalizedDescription
+ }
+ }
+ }
+ } else {
+ $PackageIDs = Get-CimInstance -query "Select SecureObjectID from SMS_DistributionPoint Where (ServerNALPath LIKE '%$DP%') AND (ObjectTypeID='$ObjectTypeID')" @hash | select -ExpandProperty SecureObjectID
+
+ $PackageIDs | foreach {
+ if ($Package = Get-CimInstance -query "Select Name from $ObjectClass WHERE PackageID='$_'" @hash) {
+ [pscustomobject]@{
+ DP = $DP
+ ObjectType = $ObjectType
+ Package = $Package.Name
+ PackageID = $_
+ }
+ }
+ }
+ }
+ } catch {
+ Write-Warning "[PROCESS] Something went wrong while querying $SCCMServer for the DP or Object info"
+ throw $_.Exception
+ }
+ }
+ }
+ end {
+ Write-Verbose -Message "[END] Ending Function"
+ Remove-CimSession -CimSession $CimSession
+ }
+}
\ No newline at end of file
diff --git a/Get-CmAppDeploymentStatus.ps1 b/Get-CmAppDeploymentStatus.ps1
new file mode 100644
index 0000000..ff36dfb
--- /dev/null
+++ b/Get-CmAppDeploymentStatus.ps1
@@ -0,0 +1,128 @@
+<#
+.SYNOPSIS
+ This script checks status of a deployed application to members of a SCCM collection or a single SCCM client
+.NOTES
+ Requires the SQL PSCX modules here https://sqlpsx.codeplex.com/documentation
+.EXAMPLE
+ PS> Get-CmAppDeploymentStatus.ps1 -CollectionName 'My Collection' -ApplicationName MyApplication
+
+ This example enumerates all collection members in the collection 'My Collection' then evaluates each of them
+ to see what the status of the application MyApplication is.
+.PARAMETER CollectionName
+ The name of the SCCM collection you'd like to query members in
+.PARAMETER Computername
+ The name of one or more PCs to check application deployment status
+.PARAMETER ApplicationName
+ The name of the application to check the status of
+.PARAMETER SiteServer
+ Your SCCM site server
+.PARAMETER SiteCode
+ The 3 character SCCM site code
+#>
+[CmdletBinding(DefaultParameterSetName = 'None')]
+param (
+ [Parameter(ParameterSetName = 'Collection', Mandatory)]
+ [string]$CollectionName,
+ [Parameter(ParameterSetName = 'Computer', Mandatory)]
+ [string[]]$Computername,
+ [string]$ApplicationName,
+ [string]$SiteServer = 'MYSITESERVER',
+ [string]$SiteCode = 'CON'
+)
+
+begin {
+ Set-StrictMode -Version Latest
+
+ function Get-CmCollectionMember ($Collection) {
+ try {
+ $Ns = "ROOT\sms\site_$SiteCode"
+ $Col = Get-CimInstance -ComputerName $SiteServer -Class 'SMS_Collection' -Namespace $Ns -Filter "Name = '$Collection'"
+ $ColId = $Col.CollectionID;
+ Get-CimInstance -Computername $SiteServer -Namespace $Ns -Class "SMS_CM_RES_COLL_$ColId"
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+
+ function Get-CmClientAppDeploymentStatus ($Computername,$ApplicationName) {
+ $EvalStates = @{
+ 0 = 'No state information is available';
+ 1 = 'Application is enforced to desired/resolved state';
+ 2 = 'Application is not required on the client';
+ 3 = 'Application is available for enforcement (install or uninstall based on resolved state). Content may/may not have been downloaded';
+ 4 = 'Application last failed to enforce (install/uninstall)';
+ 5 = 'Application is currently waiting for content download to complete';
+ 6 = 'Application is currently waiting for content download to complete';
+ 7 = 'Application is currently waiting for its dependencies to download';
+ 8 = 'Application is currently waiting for a service (maintenance) window';
+ 9 = 'Application is currently waiting for a previously pending reboot';
+ 10 = 'Application is currently waiting for serialized enforcement';
+ 11 = 'Application is currently enforcing dependencies';
+ 12 = 'Application is currently enforcing';
+ 13 = 'Application install/uninstall enforced and soft reboot is pending';
+ 14 = 'Application installed/uninstalled and hard reboot is pending';
+ 15 = 'Update is available but pending installation';
+ 16 = 'Application failed to evaluate';
+ 17 = 'Application is currently waiting for an active user session to enforce';
+ 18 = 'Application is currently waiting for all users to logoff';
+ 19 = 'Application is currently waiting for a user logon';
+ 20 = 'Application in progress, waiting for retry';
+ 21 = 'Application is waiting for presentation mode to be switched off';
+ 22 = 'Application is pre-downloading content (downloading outside of install job)';
+ 23 = 'Application is pre-downloading dependent content (downloading outside of install job)';
+ 24 = 'Application download failed (downloading during install job)';
+ 25 = 'Application pre-downloading failed (downloading outside of install job)';
+ 26 = 'Download success (downloading during install job)';
+ 27 = 'Post-enforce evaluation';
+ 28 = 'Waiting for network connectivity';
+ }
+
+ $Params = @{
+ 'Computername' = $Computername
+ 'Namespace' = 'root\ccm\clientsdk'
+ 'Class' = 'CCM_Application'
+ }
+ if ($ApplicationName) {
+ Get-WmiObject @Params | Where-Object { $_.FullName -eq $ApplicationName } | Select-Object PSComputerName, Name, InstallState, ErrorCode, @{ n = 'EvalState'; e = { $EvalStates[[int]$_.EvaluationState] } }, @{ label = 'ApplicationMadeAvailable'; expression = { $_.ConvertToDateTime($_.StartTime) } }
+ } else {
+ Get-WmiObject @Params | Select-Object PSComputerName, Name, InstallState, ErrorCode, @{ n = 'EvalState'; e = { $EvalStates[[int]$_.EvaluationState] } }, @{ label = 'ApplicationMadeAvailable'; expression = { $_.ConvertToDateTime($_.StartTime) } }
+ }
+ }
+
+ function Test-Ping ($ComputerName) {
+ try {
+ $oPing = new-object system.net.networkinformation.ping;
+ if (($oPing.Send($ComputerName, 200).Status -eq 'TimedOut')) {
+ $false
+ } else {
+ $true
+ }
+ } catch [System.Exception] {
+ $false
+ }
+ }
+}
+
+process {
+ if ($CollectionName) {
+ $Clients = (Get-CmCollectionMember -Collection $CollectionName).Name
+ } else {
+ $Clients = $Computername
+ }
+ Write-Verbose "Will query '$($Clients.Count)' clients"
+ foreach ($Client in $Clients) {
+ try {
+ if (!(Test-Ping -ComputerName $Client)) {
+ throw "$Client is offline"
+ } else {
+ $Params = @{ 'Computername' = $Client }
+ if ($ApplicationName) {
+ $Params.ApplicationName = $ApplicationName
+ }
+ Get-CmClientAppDeploymentStatus @Params
+ }
+ } catch {
+ Write-Warning $_.Exception.Message
+ }
+ }
+}
\ No newline at end of file
diff --git a/Get-DhcpLeasesInDomain.ps1 b/Get-DhcpLeasesInDomain.ps1
new file mode 100644
index 0000000..7a32480
--- /dev/null
+++ b/Get-DhcpLeasesInDomain.ps1
@@ -0,0 +1,40 @@
+#Requires -Module DhcpServer
+
+<#
+.SYNOPSIS
+ This script finds all DHCP servers in Active Directory, all scopes on all DHCP servers
+ and outputs all leases in all of those scopes
+.NOTES
+ Created on: 12/10/2014
+ Created by: Adam Bertram
+ Filename: Get-DhcpLeasesInDomain.ps1
+.EXAMPLE
+ PS> .\Get-DhcpLeasesInDomain.ps1
+
+#>
+[CmdletBinding()]
+[OutputType('Selected.Microsoft.Management.Infrastructure.CimInstance')]
+param ()
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+}
+
+process {
+ try {
+ $DhcpServers = Get-DhcpServerInDC | where { Test-Connection -ComputerName $_.DnsName -Quiet -Count 1 } | Select-Object -ExpandProperty DnsName
+ foreach ($DhcpServer in $DhcpServers) {
+ try {
+ try { $Scopes = Get-DhcpServerv4Scope -ComputerName $DhcpServer } catch {Write-Warning -Message "Error: $($_.Exception.Message)"}
+ foreach ($Scope in $Scopes) {
+ try { Get-DhcpServerv4Lease -ComputerName $DhcpServer -ScopeId $Scope.ScopeId | Select-Object * -ExcludeProperty 'Cim*' } catch { Write-Warning -Message "Error: $($_.Exception.Message)" }
+ }
+ } catch {
+ Write-Error -Message "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+ }
+} catch {
+ Write-Error -Message "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+}
+}
\ No newline at end of file
diff --git a/Get-DynamicDNSRecordsToBeScavenged.ps1 b/Get-DynamicDNSRecordsToBeScavenged.ps1
new file mode 100644
index 0000000..3ed0f54
--- /dev/null
+++ b/Get-DynamicDNSRecordsToBeScavenged.ps1
@@ -0,0 +1,111 @@
+#Requires -Module ActiveDirectory,DnsServer
+
+<#
+.SYNOPSIS
+ This script will report on all dynamic DNS records in a particular DNS zone that
+ are at risk of being scavenged by the DNS scavenging process.
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename: Get-RecordsToBeScavenged.ps1
+ Credits:
+ Requirements: An AD-integrated DNS zone
+.EXAMPLE
+ PS> Get-RecordsToBeScavenged.ps1 -DnsZone myzone -WarningDays 5
+
+ This example will find all DNS records in the zone 'myzone' that are set to be scavenged
+ within 5 days.
+.PARAMETER DnsServer
+ The DNS server that will be queried
+.PARAMETER DnsZone
+ The DNS zone that will be used to find records
+.PARAMETER WarningDays
+ The number of days ahead of scavenge time you'd like to report on. By default, this script
+ only displays DNS records set to be scavenged within 1 day.
+#>
+[CmdletBinding()]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [Parameter(Mandatory)]
+ [string]$DnsZone,
+ [Parameter()]
+ [string]$DnsServer = (Get-ADDomain).ReplicaDirectoryServers[0],
+ [Parameter()]
+ [int]$WarningDays = 1
+)
+begin {
+ function Get-DnsHostname ($IPAddress) {
+ ## Use nslookup because it's much faster than any other cmdlet
+ $Result = nslookup $IPAddress 2> $null
+ $Result| where { $_ -match 'name' } | foreach {
+ $_.Replace('Name: ', '')
+ }
+ }
+ Function Test-Ping ($ComputerName) {
+ try {
+ $oPing = new-object system.net.networkinformation.ping;
+ if (($oPing.Send($ComputerName, 200).Status -eq 'TimedOut')) {
+ $false;
+ } else {
+ $true
+ }
+ } catch [System.Exception] {
+ $false
+ }
+ }
+}
+process {
+ try {
+ ## Check if scavenging and aging is even enabled on the server and zone
+ $ServerScavenging = Get-DnsServerScavenging -Computername $DnsServer
+ $ZoneAging = Get-DnsServerZoneAging -Name $DnsZone -ComputerName $DnsServer
+ if (!$ServerScavenging.ScavengingState) {
+ Write-Warning "Scavenging not enabled on server '$DnsServer'"
+ $NextScavengeTime = 'N/A'
+ } else {
+ $NextScavengeTime = $ServerScavenging.LastScavengeTime + $ServerScavenging.ScavengingInterval
+ }
+ if (!$ZoneAging.AgingEnabled) {
+ Write-Warning "Aging not enabled on zone '$DnsZone'"
+ }
+
+ ## A record won't be scavengable until the refresh + no-refresh period has elapsed. Set a threshold
+ ## of this time plus a buffer to give the user a heads up ahead of time.
+ $StaleThreshold = ($ZoneAging.NoRefreshInterval.Days + $ZoneAging.RefreshInterval.Days) + $WarningDays
+
+ ## Find all dynamic DNS host records in the zone that haven't updated their timestamp in a long time
+ ## ensuring to only include the hosts ending with the zone name. If not, by default, Get-DnsServerResourceRecord
+ ## will include a record with and without the zone name appended
+ $StaleRecords = Get-DnsServerResourceRecord -ComputerName $DnsServer -ZoneName $DnsZone -RRType A | where { $_.TimeStamp -and ($_.Timestamp -le (Get-Date).AddDays("-$StaleThreshold")) -and ($_.Hostname -like "*.$DnsZone") }
+ foreach ($StaleRecord in $StaleREcords) {
+ ## Get the IP address of the host to preform a reverse DNS lookup later
+ $RecordIp = $StaleRecord.RecordData.IPV4Address.IPAddressToString
+ ## Perform a reverse DNS lookup to find the actual hostname for that IP address.
+ ## Sometimes when a record has been out of commission for a long time duplicate
+ ## records can be created and the actual hostname for the IP address doesn't match
+ ## the old DNS record hostname anymore.
+ $ActualHostname = Get-DnsHostname $RecordIp
+ if ($ActualHostname) {
+ ## There's a PTR record for the host record. Ping the hostname to see if it's
+ ## still online. This is to only pay attention to the computers that may still
+ ## be online but have a problem updating their record.
+ $HostOnline = Test-Ping -Computername $ActualHostname
+ } else {
+ $HostOnline = 'N/A'
+ }
+ [pscustomobject]@{
+ 'Server' = $DnsServer
+ 'Zone' = $DnsZone
+ 'RecordHostname' = $StaleRecord.Hostname
+ 'RecordTimestamp' = $StaleRecord.Timestamp
+ 'IsScavengable' = (@{ $true = $false; $false = $true }[$NextScavengeTime -eq 'N/A'])
+ 'ToBeScavengedOn' = $NextScavengeTime
+ 'ValidHostname' = $ActualHostname
+ 'RecordMatchesValidHostname' = $ActualHostname -eq $StaleRecord.Hostname
+ 'HostOnline' = (@{ $true = $HostOnline; $false = 'N/A' }[$ActualHostname -eq $StaleRecord.Hostname])
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Get-FreeHardDriveSpace.ps1 b/Get-FreeHardDriveSpace.ps1
new file mode 100644
index 0000000..b216b3c
--- /dev/null
+++ b/Get-FreeHardDriveSpace.ps1
@@ -0,0 +1,74 @@
+<#
+.SYNOPSIS
+ This finds the total hard drive free space for one or multiple hard drive partitions
+.DESCRIPTION
+ This finds the total hard drive free space for one or multiple hard drive partitions. It returns free space
+ rounded to the nearest SizeOutputLabel parameter
+.PARAMETER DriveLetter
+ This is the drive letter of the hard drive partition you'd like to query. By default, all drive letters are queried.
+.PARAMETER SizeOutputLabel
+ In what size increments you'd like the size returned (KB, MB, GB, TB). Defaults to MB.
+.PARAMETER Computername
+ The computername(s) you'd like to find free space on. This defaults to the local machine.
+.EXAMPLE
+ PS C:\> Get-DriveFreeSpace -DriveLetter 'C','D'
+ This example retrieves the free space on the C and D drive partition.
+#>
+[CmdletBinding()]
+[OutputType([array])]
+param
+(
+ [string[]]$Computername = 'localhost',
+ [Parameter(ValueFromPipeline = $true,
+ ValueFromPipelineByPropertyName = $true)]
+ [ValidatePattern('[A-Z]')]
+ [string]$DriveLetter,
+ [ValidateSet('KB','MB','GB','TB')]
+ [string]$SizeOutputLabel = 'MB'
+
+)
+
+Begin {
+ try {
+ $WhereQuery = "SELECT FreeSpace,DeviceID FROM Win32_Logicaldisk"
+
+ if ($PsBoundParameters.DriveLetter) {
+ $WhereQuery += ' WHERE'
+ $BuiltQueryParams = { @() }.Invoke()
+ foreach ($Letter in $DriveLetter) {
+ $BuiltQueryParams.Add("DeviceId = '$DriveLetter`:'")
+ }
+ $WhereQuery = "$WhereQuery $($BuiltQueryParams -join ' OR ')"
+ }
+ Write-Debug "Using WQL query $WhereQuery"
+ $WmiParams = @{
+ 'Query' = $WhereQuery
+ 'ErrorVariable' = 'MyError';
+ 'ErrorAction' = 'SilentlyContinue'
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+Process {
+ try {
+ foreach ($Computer in $Computername) {
+ $WmiParams.Computername = $Computer
+ $WmiResult = Get-WmiObject @WmiParams
+ if ($MyError) {
+ throw $MyError
+ }
+ foreach ($Result in $WmiResult) {
+ if ($Result.Freespace) {
+ [pscustomobject]@{
+ 'Computername' = $Computer;
+ 'DriveLetter' = $Result.DeviceID;
+ 'Freespace' = [int]($Result.FreeSpace / "1$SizeOutputLabel")
+ }
+ }
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+
\ No newline at end of file
diff --git a/Get-LocalGroupMembership.ps1 b/Get-LocalGroupMembership.ps1
new file mode 100644
index 0000000..4cd4476
--- /dev/null
+++ b/Get-LocalGroupMembership.ps1
@@ -0,0 +1,5 @@
+param($Computername,$Group)
+$group = [ADSI]"WinNT://$Computername/$Group"
+@($group.Invoke("Members")) |
+
+foreach { $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
\ No newline at end of file
diff --git a/Get-LocalPort.ps1 b/Get-LocalPort.ps1
new file mode 100644
index 0000000..4b6e7f2
--- /dev/null
+++ b/Get-LocalPort.ps1
@@ -0,0 +1,111 @@
+#Requires -Version 3
+<#
+.SYNOPSIS
+ This parses the native netstat.exe's output using the command line "netstat -anb" to find
+ all of the network ports in use on a local machine and all associated processes and services
+.NOTES
+ Created on: 2/15/2015
+ Created by: Adam Bertram
+ Filename: Get-LocalPort.ps1
+.EXAMPLE
+ PS> Get-LocalPort.ps1
+
+ This example will find all network ports in uses on the local computer with associated
+ processes and services
+
+.EXAMPLE
+ PS> Get-LocalPort.ps1 | Where-Object {$_.ProcessOwner -eq 'svchost.exe'}
+
+ This example will find all network ports in use on the local computer that were opened
+ by the svchost.exe process.
+
+.EXAMPLE
+ PS> Get-LocalPort.ps1 | Where-Object {$_.IPVersion -eq 'IPv4'}
+
+ This example will find all network ports in use on the local computer using IPv4 only.
+#>
+[CmdletBinding()]
+param ()
+
+begin {
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = 'Stop'
+}
+
+process {
+ try {
+ ## Capture the output of the native netstat.exe utility
+ ## Remove the top row from the result and trim off any leading or trailing spaces from each line
+ ## Replace all instances of more than 1 space with a pipe symbol. This allows easier parsing of
+ ## the fields
+ $Netstat = (netstat -anb | where {$_ -and ($_ -ne 'Active Connections')}).Trim() | Select-Object -Skip 1 | foreach {$_ -replace '\s{2,}','|'}
+
+ $i = 0
+ foreach ($Line in $Netstat) {
+ ## Create the hashtable to conver to object later
+ $Out = @{
+ 'Protocol' = ''
+ 'State' = ''
+ 'IPVersion' = ''
+ 'LocalAddress' = ''
+ 'LocalPort' = ''
+ 'RemoteAddress' = ''
+ 'RemotePort' = ''
+ 'ProcessOwner' = ''
+ 'Service' = ''
+ }
+ ## If the line is a port
+ if ($Line -cmatch '^[A-Z]{3}\|') {
+ $Cols = $Line.Split('|')
+ $Out.Protocol = $Cols[0]
+ ## Some ports don't have a state. If they do, there's always 4 fields in the line
+ if ($Cols.Count -eq 4) {
+ $Out.State = $Cols[3]
+ }
+ ## All port lines that start with a [ are IPv6
+ if ($Cols[1].StartsWith('[')) {
+ $Out.IPVersion = 'IPv6'
+ $Out.LocalAddress = $Cols[1].Split(']')[0].TrimStart('[')
+ $Out.LocalPort = $Cols[1].Split(']')[1].TrimStart(':')
+ if ($Cols[2] -eq '*:*') {
+ $Out.RemoteAddress = '*'
+ $Out.RemotePort = '*'
+ } else {
+ $Out.RemoteAddress = $Cols[2].Split(']')[0].TrimStart('[')
+ $Out.RemotePort = $Cols[2].Split(']')[1].TrimStart(':')
+ }
+ } else {
+ $Out.IPVersion = 'IPv4'
+ $Out.LocalAddress = $Cols[1].Split(':')[0]
+ $Out.LocalPort = $Cols[1].Split(':')[1]
+ $Out.RemoteAddress = $Cols[2].Split(':')[0]
+ $Out.RemotePort = $Cols[2].Split(':')[1]
+ }
+ ## Because the process owner and service are on separate lines than the port line and the number of lines between them is variable
+ ## this craziness was necessary. This line starts parsing the netstat output at the current port line and searches for all
+ ## lines after that that are NOT a port line and finds the first one. This is how many lines there are until the next port
+ ## is defined.
+ $LinesUntilNextPortNum = ($Netstat | Select-Object -Skip $i | Select-String -Pattern '^[A-Z]{3}\|' -NotMatch | Select-Object -First 1).LineNumber
+ ## Add the current line to the number of lines until the next port definition to find the associated process owner and service name
+ $NextPortLineNum = $i + $LinesUntilNextPortNum
+ ## This would contain the process owner and service name
+ $PortAttribs = $Netstat[($i+1)..$NextPortLineNum]
+ ## The process owner is always enclosed in brackets of, if it can't find the owner, starts with 'Can'
+ $Out.ProcessOwner = $PortAttribs -match '^\[.*\.exe\]|Can'
+ if ($Out.ProcessOwner) {
+ ## Get rid of the brackets and pick the first index because this is an array
+ $Out.ProcessOwner = ($Out.ProcessOwner -replace '\[|\]','')[0]
+ }
+ ## A service is always a combination of multiple word characters at the start of the line
+ if ($PortAttribs -match '^\w+$') {
+ $Out.Service = ($PortAttribs -match '^\w+$')[0]
+ }
+ [pscustomobject]$Out
+ }
+ ## Keep the counter
+ $i++
+ }
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+}
\ No newline at end of file
diff --git a/Get-MoreCowbell.ps1 b/Get-MoreCowbell.ps1
new file mode 100644
index 0000000..58b6802
--- /dev/null
+++ b/Get-MoreCowbell.ps1
@@ -0,0 +1,58 @@
+function Get-MoreCowbell
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter()]
+ [ValidateNotNullOrEmpty()]
+ [switch]$Introduction,
+
+ [Parameter()]
+ [ValidateNotNullOrEmpty()]
+ [int]$Repeat = 10,
+
+ [Parameter()]
+ [ValidateNotNullOrEmpty()]
+ [string]$CowbellUrl = 'http://emmanuelprot.free.fr/Drums%20kit%20Manu/Cowbell.wav',
+
+ [Parameter()]
+ [ValidateNotNullOrEmpty()]
+ [string]$IntroUrl = 'http://www.innervation.com/crap/cowbell.wav'
+
+
+ )
+ begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ }
+ process {
+ try
+ {
+ $sound = new-Object System.Media.SoundPlayer
+ $CowBellLoc = "$($env:TEMP)\Cowbell.wav"
+ if (-not (Test-Path -Path $CowBellLoc -PathType Leaf))
+ {
+ Invoke-WebRequest -Uri $CowbellUrl -OutFile $CowBellLoc
+ }
+ if ($Introduction.IsPresent)
+ {
+ $IntroLoc = "$($env:TEMP)\CowbellIntro.wav"
+ if (-not (Test-Path -Path $IntroLoc -PathType Leaf))
+ {
+ Invoke-WebRequest -Uri $IntroUrl -OutFile $IntroLoc
+ }
+ $sound.SoundLocation = $IntroLoc
+ $sound.Play()
+ sleep 2
+ }
+ $sound.SoundLocation = $CowBellLoc
+ for ($i=0; $i -lt $Repeat; $i++) {
+ $sound.Play();
+ Start-Sleep -Milliseconds 500
+ }
+ }
+ catch
+ {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
\ No newline at end of file
diff --git a/Get-RecordsToBeScavenged.ps1 b/Get-RecordsToBeScavenged.ps1
new file mode 100644
index 0000000..61299b0
--- /dev/null
+++ b/Get-RecordsToBeScavenged.ps1
@@ -0,0 +1,178 @@
+#Requires -Version 3
+#Requires -Module ActiveDirectory,DnsServer
+
+<#
+.SYNOPSIS
+ This script will report on all dynamic DNS records in a particular DNS zone that
+ are at risk of being scavenged by the DNS scavenging process.
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename: Get-RecordsToBeScavenged.ps1
+ Credits:
+ Requirements: An AD-integrated DNS zone
+.EXAMPLE
+ PS> Get-RecordsToBeScavenged.ps1 -DnsZone myzone -WarningDays 5
+
+ This example will find all DNS records in the zone 'myzone' that are set to be scavenged
+ within 5 days.
+.PARAMETER DnsServer
+ The DNS server that will be queried
+.PARAMETER DnsZone
+ The DNS zone that will be used to find records
+.PARAMETER CheckValidity
+ Use this switch parameter to verify each record that's showing as "stale". This attempts to check
+ a few criteria like ensuring the stale record actually represents the hostname and if that host
+ is currently online.
+.PARAMETER WarningDays
+ The number of days ahead of scavenge time you'd like to report on. By default, this script
+ only displays DNS records set to be scavenged within 1 day.
+.PARAMETER EmailAddress
+ An email address that you'd like to send the list of stale DNS records to.
+.PARAMETER NetbiosDomainName
+ The single-label of the Active Directory domain you're in
+.PARAMETER NbtScanFilePath
+ The file path of the nbtscan.exe utility
+#>
+[CmdletBinding()]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [Parameter(Mandatory)]
+ [string]$DnsZone,
+ [Parameter()]
+ [switch]$CheckValidity,
+ [Parameter()]
+ [string]$DnsServer = (Get-ADDomain).ReplicaDirectoryServers[0],
+ [Parameter()]
+ [int]$WarningDays = 1,
+ [string]$EmailAddress,
+ [Parameter()]
+ [string]$NetbiosDomainName = (Get-AdDomain).NetBIOSName,
+ [Parameter()]
+ [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
+ [string]$NbtScanFilePath = 'C:\nbtscan.exe'
+)
+begin {
+ function Get-DnsHostname ($IPAddress) {
+ try {
+ ## Use nslookup because it's much faster than any other cmdlet
+ $Result = nslookup $IPAddress 2> $null
+ $Result | where { $_ -match 'name' } | foreach {
+ $_.Replace('Name: ', '')
+ }
+ } catch {
+ Write-Warning "Could not find DNS hostname for IP $IpAddress"
+ $false
+ }
+ }
+ Function Test-Ping ($ComputerName) {
+ $Result = ping $Computername -n 2
+ if ($Result | where { $_ -match 'Reply from ' }) {
+ $true
+ } else {
+ $false
+ }
+ }
+
+ function Get-Computername ($IpAddress) {
+ try {
+ & $NbtScanFilePath $IpAddress 2> $null | where { $_ -match "$NetbiosDomainName\\(.*) " } | foreach { $matches[1].Trim() }
+ } catch {
+ Write-Warning -Message "Failed to get computer name for IP $IpAddress"
+ $false
+ }
+ }
+}
+process {
+ try {
+ ## Check if scavenging and aging is even enabled on the server and zone
+ $ServerScavenging = Get-DnsServerScavenging -Computername $DnsServer
+ $ZoneAging = Get-DnsServerZoneAging -Name $DnsZone -ComputerName $DnsServer
+ if (!$ServerScavenging.ScavengingState) {
+ Write-Warning "Scavenging not enabled on server '$DnsServer'"
+ $NextScavengeTime = 'N/A'
+ } else {
+ $NextScavengeTime = $ServerScavenging.LastScavengeTime + $ServerScavenging.ScavengingInterval
+ Write-Verbose "The next scavenge time is '$NextScavengeTime'"
+ }
+ if (!$ZoneAging.AgingEnabled) {
+ Write-Warning "Aging not enabled on zone '$DnsZone'"
+ $NextScavengeTime = 'N/A'
+ }
+
+ ## A record won't be scavengable until the refresh + no-refresh period has elapsed. Set a threshold
+ ## of this time plus a buffer to give the user a heads up ahead of time.
+ $StaleThreshold = ($ZoneAging.NoRefreshInterval.Days + $ZoneAging.RefreshInterval.Days) + $WarningDays
+ Write-Verbose "The stale threshold is '$StaleThreshold' days"
+
+ ## Find all dynamic DNS host records in the zone that haven't updated their timestamp in a long time
+ ## ensuring to only include the hosts ending with the zone name. If not, by default, Get-DnsServerResourceRecord
+ ## will include a record with and without the zone name appended
+ $StaleRecords = Get-DnsServerResourceRecord -ComputerName $DnsServer -ZoneName $DnsZone -RRType A | where { $_.TimeStamp -and ($_.Timestamp -le (Get-Date).AddDays("-$StaleThreshold")) -and ($_.Hostname -like "*.$DnsZone") }
+ Write-Verbose "Found '$($StaleRecords.Count)' stale records in zone '$DnsZone' on server '$DnsServer'"
+ $EmailRecords = @()
+ foreach ($StaleRecord in $StaleRecords) {
+ $Output = @{
+ 'Server' = $DnsServer
+ 'Zone' = $DnsZone
+ 'RecordHostname' = $StaleRecord.Hostname
+ 'RecordTimestamp' = $StaleRecord.Timestamp
+ 'ReversePTRRecord' = 'N/A'
+ 'ReverseNetBIOSHostname' = 'N/A'
+ 'IsScavengable' = (@{ $true = $false; $false = $true }[$NextScavengeTime -eq 'N/A'])
+ 'ToBeScavengedOn' = $NextScavengeTime
+ 'RecordMatchesNetBiosHostname' = 'N/A'
+ 'HostOnline' = 'N/A'
+ }
+ if ($CheckValidity.IsPresent) {
+ Write-Verbose "Checking stale record '$($StaleRecord.Hostname)'"
+ ## Get the IP address of the host to preform a reverse DNS lookup later
+ $RecordIp = $StaleRecord.RecordData.IPV4Address.IPAddressToString
+ $ReverseDnsHostname = Get-DnsHostname -IPAddress $RecordIp
+ if ($ReverseDnsHostname) {
+ Write-Verbose "DNS PTR record is '$ReverseDnsHostname'"
+ $Output.ReversePTRRecord = $ReverseDnsHostname
+ } else {
+ Write-Verbose "No DNS PTR record exists for record '$($StaleRecord.Hostname)'"
+ }
+ $ReverseNetbiosHostname = Get-Computername -IpAddress $RecordIp
+ if ($ReverseNetbiosHostname) {
+ Write-Verbose "Netbios hostname is '$ReverseNetbiosHostname'"
+ $Output.ReverseNetBIOSHostname = $ReverseNetbiosHostname
+ } else {
+ Write-Verbose "No Netbios hostname exists for record '$($StaleRecord.Hostname)'"
+ }
+
+ if ($ReverseNetbiosHostname -eq $StaleRecord.Hostname.Replace(".$DnsZone", '')) {
+ Write-Verbose "Netbios hostname matches DNS record hostname"
+ $Output.RecordMatchesNetBiosHostname = $true
+ $Output.HostOnline = Test-Ping -Computername $ReverseNetbiosHostname
+ }
+ }
+ if ($EmailAddress) {
+ $EmailRecords += [pscustomobject]$Output
+ } else {
+ [pscustomobject]$Output
+ }
+ }
+ if ($EmailAddress) {
+ $EmailRecords = @([pscustomobject]@{ 'test'='dfdfdfd'}, [pscustomobject]@{ 'teddddst' = 'dfdfdddddfd' })
+ if ($EmailRecords.Count -eq 0) {
+ Write-Verbose "No stale records found to email"
+ } else {
+ Write-Verbose "Emailing the list of $($EmailRecords.Count) stale records to $EmailAddress"
+ $Params = @{
+ 'From' = 'Union Hospital ';
+ 'To' = $EmailAddress;
+ 'Subject' = 'UNH DNS Records To be Scavenged';
+ 'SmtpServer' = 'smtp.uhhg.org'
+ 'Body' = $EmailRecords | Out-String
+ }
+
+ Send-MailMessage @Params
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Get-ServiceListeningPort.ps1 b/Get-ServiceListeningPort.ps1
new file mode 100644
index 0000000..eb891c0
--- /dev/null
+++ b/Get-ServiceListeningPort.ps1
@@ -0,0 +1,57 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename:
+ Credits:
+ Requirements:
+ Todos:
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER PARAM1
+
+.PARAMETER PARAM2
+
+#>
+[CmdletBinding(DefaultParameterSetName = 'name')]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [Parameter(ParameterSetName = 'name',
+ Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateSet("Tom","Dick","Jane")]
+ [ValidateRange(21,65)]
+ [ValidateScript({Test-Path $_ -PathType 'Container'})]
+ [ValidateNotNullOrEmpty()]
+ [ValidateCount(1,5)]
+ [ValidateLength(1,10)]
+ [ValidatePattern()]
+ [string]$Computername = 'localhost'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+}
+
+process {
+ try {
+ $ServiceExes = Get-WmiObject -ComputerName $Computername -Property DisplayName, PathName
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+end {
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Get-UpTime.ps1 b/Get-UpTime.ps1
new file mode 100644
index 0000000..2fdfb9e
--- /dev/null
+++ b/Get-UpTime.ps1
@@ -0,0 +1,43 @@
+<#
+.SYNOPSIS
+ This script gets the total time a computer has been up for.
+.DESCRIPTION
+ This script queries the Win32_OperatingSystem WMI class to retrieve the
+ LastBootupTime and output a datetime object containing the specific
+ time the computer has been up for.
+.NOTES
+ Created on: 6/9/2014
+ Created by: Adam Bertram
+ Filename: Get-UpTime.ps1
+.EXAMPLE
+ .\Get-Uptime.ps1 -Computername 'COMPUTER'
+
+.PARAMETER Computername
+ This is the name of the computer you'd like to query
+#>
+[CmdletBinding()]
+param (
+ [Parameter(ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string]$Computername = 'localhost'
+)
+
+begin {
+ Set-StrictMode -Version Latest
+}
+
+process {
+ try {
+ $WmiResult = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName
+ $LastBoot = $WmiResult.ConvertToDateTime($WmiResult.LastBootupTime)
+ $ObjParams = [ordered]@{'Computername' = $Computername }
+ ((Get-Date) - $LastBoot).psobject.properties | foreach { $ObjParams[$_.Name] = $_.Value }
+ New-Object -TypeName PSObject -Property $ObjParams
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+end {
+
+}
\ No newline at end of file
diff --git a/Import-Certificate.ps1 b/Import-Certificate.ps1
new file mode 100644
index 0000000..c07d0eb
--- /dev/null
+++ b/Import-Certificate.ps1
@@ -0,0 +1,69 @@
+<#
+.SYNOPSIS
+ This script imports a certificate into any certificate store on a local computer
+.NOTES
+ Created on: 8/6/2014
+ Created by: Adam Bertram
+ Filename: Import-Certificate.ps1
+.EXAMPLE
+ PS> .\Import-Certificate.ps1 -Location LocalMachine -StoreName My -FilePath C:\certificate.cer
+
+ This example will import the certificate.cert certificate into the Personal store for the
+ local computer
+.EXAMPLE
+ PS> .\Import-Certificate.ps1 -Location CurrentUser -StoreName TrustedPublisher -FilePath C:\certificate.cer
+
+ This example will import the certificate.cer certificate into the Trusted Publishers store for the
+ currently logged on user
+.PARAMETER Location
+ This is the location (either CurrentUser or LocalMachine) where the store is located which the certificate
+ will go into
+.PARAMETER StoreName
+ This is the certificate store that the certificate will be placed into
+.PARAMETER FilePath
+ This is the path to the certificate file that you'd like to import
+#>
+[CmdletBinding()]
+[OutputType()]
+param (
+ [Parameter(Mandatory)]
+ [ValidateSet('CurrentUser', 'LocalMachine')]
+ [string]$Location,
+ [Parameter(Mandatory)]
+ [ValidateScript({
+ if ($Location -eq 'CurrentUser') {
+ (Get-ChildItem Cert:\CurrentUser | select -ExpandProperty name) -contains $_
+ } else {
+ (Get-ChildItem Cert:\LocalMachine | select -ExpandProperty name) -contains $_
+ }
+ })]
+ [string]$StoreName,
+ [Parameter(Mandatory)]
+ [ValidateScript({Test-Path $_ -PathType Leaf })]
+ [string]$FilePath
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ [void][System.Reflection.Assembly]::LoadWithPartialName("System.Security")
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+ $Cert = Get-Item $FilePath
+ $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $Cert
+ foreach ($Store in $StoreName) {
+ $X509Store = New-Object System.Security.Cryptography.X509Certificates.X509Store $Store, $Location
+ $X509Store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
+ $X509Store.Add($Cert)
+ $X509Store.Close()
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Invoke-RemoteScript.ps1 b/Invoke-RemoteScript.ps1
new file mode 100644
index 0000000..efa4c39
--- /dev/null
+++ b/Invoke-RemoteScript.ps1
@@ -0,0 +1,97 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 5/9/2014 1:48 PM
+ Created by: Adam Bertram
+ Organization:
+ Filename:
+.DESCRIPTION
+
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER FolderPath
+ Any folders (on the local computer) that need copied to the remote computer prior to execution
+.PARAMETER ScriptPath
+ The Powershell script path (on the local computer) that needs executed on the remote computer
+.PARAMETER RemoteDrive
+ The remote drive letter the script will be executed on and the folder will be copied to
+.PARAMETER Computername
+ The remote computer to execute files on.
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $True,
+ ValueFromPipelineByPropertyName = $True)]
+ [string]$Computername,
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [string]$FolderPath,
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [string]$ScriptPath,
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [string]$RemoteDrive = 'C'
+)
+
+begin {
+ ## http://www.leeholmes.com/blog/2009/11/20/testing-for-powershell-remoting-test-psremoting/
+ function Test-PsRemoting {
+ param (
+ [Parameter(Mandatory = $true)]
+ $computername
+ )
+
+ try {
+ $errorActionPreference = "Stop"
+ $result = Invoke-Command -ComputerName $computername { 1 }
+ } catch {
+ Write-Verbose $_
+ return $false
+ }
+
+ ## I’ve never seen this happen, but if you want to be
+ ## thorough….
+ if ($result -ne 1) {
+ Write-Verbose "Remoting to $computerName returned an unexpected result."
+ return $false
+ }
+ $true
+ }
+
+ Write-Verbose "Validating prereqs for remote script execution..."
+ if (!(Test-Path $FolderPath)) {
+ throw 'Folder path does not exist'
+ } elseif (!(Test-Path $ScriptPath)) {
+ throw 'Script path does not exist'
+ } elseif ((Get-ItemProperty -Path $ScriptPath).Extension -ne '.ps1') {
+ throw 'Script specified is not a Powershell script'
+ } elseif (!(Test-Ping $Computername)) {
+ throw 'Computer is not reachable'
+ }
+ $ScriptName = $ScriptPath | Split-Path -Leaf
+ $RemoteFolderPath = $FolderPath | Split-Path -Leaf
+ $RemoteScriptPath = "$RemoteDrive`:\$RemoteFolderPath\$ScriptName"
+}
+
+process {
+ Write-Verbose "Copying the folder $FolderPath to the remote computer $ComputerName..."
+ Copy-Item $FolderPath -Recurse "\\$Computername\$RemoteDrive`$" -Force
+ Write-Verbose "Copying the script $ScriptName to the remote computer $ComputerName..."
+ Copy-Item $ScriptPath "\\$Computername\$RemoteDrive`$\$RemoteFolderPath" -Force
+ Write-Verbose "Executing $RemoteScriptPath on the remote computer $ComputerName..."
+ #Invoke-Command -ComputerName $Computername -ScriptBlock { Start-Process powershell.exe -ArgumentList $using:RemoteScriptPath }
+ ([WMICLASS]"\\$Computername\Root\CIMV2:Win32_Process").create("powershell.exe -File $RemoteScriptPath -NonInteractive -NoProfile")
+}
+
+end {
+ #Write-Verbose "Cleaning up the copied folder and script from remote computer $Computername..."
+ #Remove-Item "\\$ComputerName\$RemoteDrive`$\$RemoteFolderPath" -Recurse -Force
+}
\ No newline at end of file
diff --git a/Modules/CmSiteMaintenanceTasks.psm1 b/Modules/CmSiteMaintenanceTasks.psm1
new file mode 100644
index 0000000..0e33ec0
--- /dev/null
+++ b/Modules/CmSiteMaintenanceTasks.psm1
@@ -0,0 +1,119 @@
+function Get-CmSiteMaintenanceTask {
+ <#
+ .SYNOPSIS
+ This function discovers and records the state of all site maintenance tasks on a ConfigMgr site server.
+ .PARAMETER TaskName
+ The name of the site maintenance task you'd like to limit the result set by. This accepts wildcards or
+ multiple names
+ .PARAMETER Status
+ The status (either enabled or disabled) of the site maintenance tasks you'd like to limit the result set by.
+ .PARAMETER SiteServer
+ The SCCM site server to query
+ .PARAMETER SiteCode
+ The SCCM site code
+ .EXAMPLE
+
+ PS> Get-CmSiteMaintenanceTask -TaskName 'Disabled*' -Status Enabled
+
+ This example finds all site maintenance tasks starting with 'Disabled' that are enabled.
+ #>
+ [CmdletBinding()]
+ [OutputType([System.Management.ManagementObject])]
+ param (
+ [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
+ [string[]]$TaskName,
+ [Alias('ItemName')]
+ [ValidateSet('Enabled', 'Disabled')]
+ [string]$Status,
+ [string]$SiteServer = 'CONFIGMANAGER',
+ [ValidateLength(3, 3)]
+ [string]$SiteCode = 'UHP'
+ )
+
+ process {
+ try {
+ $WmiParams = @{ 'Computername' = $SiteServer; 'Namespace' = "root\sms\site_$SiteCode"}
+
+ Write-Verbose -Message "Building the WMI query..."
+ if ($TaskName -or $Status) {
+ if ($TaskName) {
+ $WmiParams.Query = 'SELECT * FROM SMS_SCI_SQLTask WHERE '
+ $NameConditions = @()
+ foreach ($n in $TaskName) {
+ ## Allow asterisks in cmdlet but WQL requires percentage and double backslashes
+ $NameValue = $n.Replace('*', '%').Replace('\', '\\')
+ $Operator = @{ $true = 'LIKE'; $false = '=' }[$NameValue -match '\%']
+ $NameConditions += "(ItemName $Operator '$NameValue')"
+ }
+ $WmiParams.Query += ($NameConditions -join ' OR ')
+ }
+ if ($Status) {
+ $WmiParams.Class = 'SMS_SCI_SQLTask'
+ $Enabled = $Status -eq 'Enabled'
+ $WhereBlock = { $_.Enabled -eq $Enabled }
+ }
+ } else {
+ $WmiParams.Class = 'SMS_SCI_SQLTask'
+ }
+ if ($WhereBlock) {
+ Get-WmiObject @WmiParams | where $WhereBlock
+ } else {
+ Get-WmiObject @WmiParams
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
+
+function Enable-CmSiteMaintenanceTask {
+ <#
+ .SYNOPSIS
+ This function enables a ConfigMgr site maintenance task.
+ .PARAMETER InputObject
+ An object of returned from Get-CmSiteMaintenceTask of the task you'd like enabled.
+ .EXAMPLE
+
+ PS> Get-CmSiteMaintenanceTask -TaskName 'Disabled*' -Status Disabled | Enable-CmsiteMaintenanceTask
+
+ This example finds all site maintenance tasks starting with 'Disabled' that are disabled and enables them all.
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(ValueFromPipeline)]
+ [System.Management.ManagementObject]$InputObject
+ )
+ process {
+ try {
+ $InputObject | Set-WmiInstance -Arguments @{ 'Enabled' = $true } | Out-Null
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
+
+function Disable-CmSiteMaintenanceTask {
+ <#
+ .SYNOPSIS
+ This function disables a ConfigMgr site maintenance task.
+ .PARAMETER InputObject
+ An object of returned from Get-CmSiteMaintenceTask of the task you'd like disabled.
+ .EXAMPLE
+
+ PS> Get-CmSiteMaintenanceTask -TaskName 'Disabled*' -Status Enabled | Disable-CmsiteMaintenanceTask
+
+ This example finds all site maintenance tasks starting with 'Disabled' that are enabled and disables them all.
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(ValueFromPipeline)]
+ [System.Management.ManagementObject]$InputObject
+ )
+ process {
+ try {
+ $InputObject | Set-WmiInstance -Arguments @{ 'Enabled' = $false } | Out-Null
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
\ No newline at end of file
diff --git a/FileMonitor.psm1 b/Modules/FileMonitor.psm1
similarity index 100%
rename from FileMonitor.psm1
rename to Modules/FileMonitor.psm1
diff --git a/PSModulePath.psm1 b/Modules/PSModulePath.psm1
similarity index 100%
rename from PSModulePath.psm1
rename to Modules/PSModulePath.psm1
diff --git a/Modules/PSRemoting.psm1 b/Modules/PSRemoting.psm1
new file mode 100644
index 0000000..e6b84db
--- /dev/null
+++ b/Modules/PSRemoting.psm1
@@ -0,0 +1,45 @@
+#Requires -Version 4
+
+function Test-PsRemoting {
+ <#
+ .SYNOPSIS
+ This function tests if PS remoting is enabled on a remote computer and (if used) will attempt
+ to connect with a local username and password.
+ .PARAMETER Computername
+ The remote computer name
+ .PARAMETER LocalCredential
+ An optional credential object representing the remote computer's local username and password
+ .PARAMETER DomainCredential
+ If the remote computer and the executing computer are both domain-joined, this is an optional
+ credential object representing a domain username and password to test against. By default, it
+ will test against the currently logged on user.
+ #>
+ [CmdletBinding(DefaultParameterSetName = 'None')]
+ param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string[]]$Computername,
+ [Parameter(Mandatory,ParameterSetName = 'LocalCredential')]
+ [System.Management.Automation.PSCredential]$LocalCredential,
+ [Parameter(Mandatory, ParameterSetName = 'DomainCredential')]
+ [System.Management.Automation.PSCredential]$DomainCredential
+ )
+ process {
+ foreach ($Computer in $Computername) {
+ try {
+ if (!$LocalCredential -and !$DomainCredential) {
+ ## Test remoting under the currently logged on user
+ Invoke-Command -ComputerName $Computer -ScriptBlock { 1 }
+ $true
+ } elseif ($LocalCredential) { ## Test remoting with a local username and password
+
+ } else { ## Test remoting with a domain username and password
+
+ }
+ } catch {
+ [pscustomobject]@{ 'Computer' = $Computer; 'Result' = $false; 'Error' = $_.Exception.Message }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/New-CMMyApplication.ps1 b/New-CMMyApplication.ps1
new file mode 100644
index 0000000..9fec1d5
--- /dev/null
+++ b/New-CMMyApplication.ps1
@@ -0,0 +1,168 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 7/19/2014
+ Created by: Adam Bertram
+ Filename:
+ Credits:
+ Requirements:
+ Todos:
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER PARAM1
+
+.PARAMETER PARAM2
+
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory)]
+ [string]$Name,
+ [Parameter(Mandatory)]
+ [string]$Manufacturer,
+ [Parameter(Mandatory)]
+ [ValidatePattern('[-+]?([0-9]*\.[0-9]+|[0-9]+)')]
+ [string]$SoftwareVersion,
+ [Parameter()]
+ [string]$Owner = 'Adam Bertram',
+ [Parameter()]
+ [string]$SupportContact = 'Adam Bertram',
+ [Parameter(Mandatory)]
+ [ValidateScript({ Test-Path -Path $_ -PathType Container })]
+ [string]$SourceFolderPath,
+ [Parameter(Mandatory)]
+ [string]$InstallationProgram,
+ [Parameter()]
+ [ValidateScript({ Test-Path -Path $_ -PathType Container })]
+ [string]$RootPackageFolderPath = '\\hosp.uhhg.org\dfs\softwarelibrary\software_packages',
+ [Parameter()]
+ [ValidateScript({ Test-Path -Path $_ -PathType 'Leaf' })]
+ [string]$IconLocationFilePath,
+ [Parameter()]
+ [string]$InstallationBehaviorType = 'InstallForSystem',
+ [Parameter()]
+ [string]$InstallationProgramVisibility = 'Hidden',
+ [Parameter()]
+ [string]$MaximumAllowedRunTimeMinutes = '15',
+ [Parameter()]
+ [string]$EstimatedInstallationTimeMinutes = '5',
+ [Parameter()]
+ [string]$RebootBehavior = 'ForceReboot',
+ [Parameter()]
+ [string]$DistributionPointGroup = 'All DPs',
+ [Parameter()]
+ [string]$SiteCode = 'UHP',
+ [Parameter()]
+ [string]$InstallScriptTemplateFilePath = '\\hosp.uhhg.org\dfs\softwarelibrary\software_packages\_Template_Files\install.ps1'
+
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ Write-Verbose 'Ensuring the ConfigurationManager module is available and importing it...'
+ ## Ensure the ConfigurationManager module is available
+ if (!(Test-Path "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1")) {
+ throw 'Configuration Manager module not found. Is the admin console intalled?'
+ } elseif (!(Get-Module 'ConfigurationManager')) {
+ Import-Module "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1"
+ }
+ Write-Verbose 'Performing prereq setup things...'
+ $Location = (Get-Location).Path
+
+ ## Replace any spaces in the attributes with backspaces
+ $PackageFolderName = "$($Manufacturer.Replace(' ','_'))_$($Name.Replace(' ','_'))_$($SoftwareVersion.Replace(' ','_'))"
+ $ContentFolderPath = "$RootPackageFolderPath\$PackageFolderName"
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+ ## Create the source folder path to place the source content then copy all of the contents
+ ## from the original source folder to the package source folder.
+ Write-Verbose "Creating the content folder '$ContentFolderPath'..."
+ if (!(Test-Path $ContentFolderPath)) {
+ mkdir $ContentFolderPath | Out-Null
+ } else {
+ throw "The folder at '$ContentFolderPath' already exists."
+ }
+
+ ## Copy source content to the content folder
+ Write-Verbose "Copying files from source folder '$SourceFolderPath' to '$ContentFolderPath'..."
+ Copy-Item -Path "$SourceFolderPath\*" -Destination $ContentFolderPath -Recurse -Force
+
+ ## Copy the install.ps1 script template (import-module, $workingdir, start-log, etc)
+ Write-Verbose "Copying the installer template fiel at '$InstallScriptTemplateFilePath' to '$ContentFolderPath'..."
+ Copy-Item -Path $InstallScriptTemplateFilePath -Destination $ContentFolderPath
+
+ ## Create the application container. This will hold our deployment type
+ Write-Verbose "Creating the application '$Name'..."
+ $NewCmApplicationParams = @{
+ 'Name' = $Name;
+ 'Owner' = $Owner;
+ 'SupportContact' = $SupportContact;
+ 'IconLocationFile' = $IconLocationFilePath;
+ 'Publisher' = $Manufacturer;
+ 'SoftwareVersion' = $SoftwareVersion
+ }
+ Set-Location "$($SiteCode):"
+ New-CMApplication @NewCmApplicationParams | Out-Null
+
+ ## Create the deployment type
+ Write-Verbose 'Creating the deployment type...'
+ $AddCmDeploymentTypeParams = @{
+ 'ApplicationName' = $ApplicationName;
+ 'ScriptInstaller' = $true;
+ 'ManualSpecifyDeploymentType' = $true;
+ 'DeploymentTypeName' = "Deploy $Name";
+ 'InstallationProgram' = $InstallationProgram;
+ 'ContentLocation' = $ContentFolderPath;
+ ## TODO: This param is ignored
+ ##'LogonRequirementType' = 'WhereOrNotUserLoggedOn';
+ 'InstallationBehaviorType' = $InstallationBehaviorType;
+ 'InstallationProgramVisibility' = $InstallationProgramVisibility;
+ 'MaximumAllowedRunTimeMinutes' = $MaximumAllowedRunTimeMinutes;
+ 'EstimatedInstallationTimeMinutes' = $EstimatedInstallationTimeMinutes;
+ #'DetectDeploymentTypeByCustomScript' = $true;
+ #'ScriptType' = 'Powershell';
+ #'ScriptContent' = ((Get-Content $InstallerDetectionScriptFilePath) -join "`r`n")
+ }
+ Add-CMDeploymentType @AddCmDeploymentTypeParams
+
+ ## Set reboot behavior because it can't be set with Add-CMDeploymentType
+ Write-Verbose 'Setting reboot behavior for the deployment type...'
+ $SetCmDeploymentTypeParams = @{
+ 'RebootBehavior' = $RebootBehavior;
+ 'DeploymentTypeName' = "Deploy $ApplicationName";
+ 'ApplicationName' = $ApplicationName;
+ 'MsiOrScriptInstaller' = $true;
+ }
+ Set-CMDeploymentType @SetCmDeploymentTypeParams
+
+ ## distribute to DPs
+ Write-Verbose 'Distributing content...'
+ Start-CMContentDistribution -DistributionPointGroupName $DistributionPointGroup -PackageName $Name
+
+ ## Create the OSD package
+ Write-Verbose 'Creating the OSD package...'
+ $PackageConversionScriptFilePath = 'C:\Dropbox\Powershell\scripts\complete\Convert-CMApplicationToPackage.ps1'
+ if (Test-Path $PackageConversionScriptFilePath) {
+ & $PackageConversionScriptFilePath -ApplicationName $Name -SkipRequirements -DistributeContent -OsdFriendlyPowershellSyntax
+ } else {
+ Write-Warning "OSD package could not be created because the script '$PackageConversionScriptFilePath' does not exist"
+ }
+
+ Write-Verbose 'Done.'
+
+ #Unlock-CMObject $NewPackage
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
\ No newline at end of file
diff --git a/New-Invoice/InvoiceTemplate.xml b/New-Invoice/InvoiceTemplate.xml
new file mode 100644
index 0000000..8124898
--- /dev/null
+++ b/New-Invoice/InvoiceTemplate.xml
@@ -0,0 +1,16 @@
+
+
+
+ Adam Bertram
+ 555 Address Ln
+ MyCity, CA
+ 55555
+ myemail@gmail.com
+ 555-444-3333
+
+
+
+
+
+
+
diff --git a/New-Invoice/InvoiceTemplate.xsl b/New-Invoice/InvoiceTemplate.xsl
new file mode 100644
index 0000000..fe9985e
--- /dev/null
+++ b/New-Invoice/InvoiceTemplate.xsl
@@ -0,0 +1,312 @@
+
+
+
+
+ <!DOCTYPE html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/New-Invoice/NReco.PdfGenerator.dll b/New-Invoice/NReco.PdfGenerator.dll
new file mode 100644
index 0000000..6a46fd8
Binary files /dev/null and b/New-Invoice/NReco.PdfGenerator.dll differ
diff --git a/New-Invoice/New-Invoice.ps1 b/New-Invoice/New-Invoice.ps1
new file mode 100644
index 0000000..7c1eda8
--- /dev/null
+++ b/New-Invoice/New-Invoice.ps1
@@ -0,0 +1,213 @@
+<#
+.SYNOPSIS
+ Creates a HTML, XML and PDF invoice.
+.DESCRIPTION
+ Based off of a XML template, this script reads the template and adds any new field values
+ passed to it via parameters from this script. It then creates either a XML, HTML or PDF file
+ based on the extension of the output file you specify.
+.NOTES
+ Created on: 11/15/2014
+ Created by: Adam Bertram
+ Filename: New-Invoice.ps1
+.EXAMPLE
+ PS> .\New-Invoice.ps1 -InvoiceTitle 'New Title' -InvoiceNumber '12334' -ClientCompany 'new client' -ClientName 'new clent name' -Item @{'Description'='mydsc';'Price'='445';'Quantity'='6'} -Force -TopNote 'this is the top note' -BottomNote 'this is the bottom note' -InvoiceFilePath c:\invoice.pdf
+
+ This example will create a PDF invoice in C:\ called invoice.pdf with a single item and will overwrite any invoice that exists in that location.
+.PARAMETER InvoiceTitle
+ The title of the invoice
+.PARAMETER InvoiceNumber
+ The invoice number
+.PARAMETER ClientCompany
+ Your client's company name
+.PARAMETER ClientName
+ The person in the company you'd like to send the invoice to
+.PARAMETER Item
+ One or more items you'd like to add to the invoice. This can either be a single item as
+ a hashtable or multiple hashtables all with required keys of Price,Description and Quantity
+.PARAMETER TopNote
+ The text to display above the item table
+.PARAMETER BottomNote
+ The text to display below the item table
+.PARAMETER Force
+ Use this to overwrite both file paths $XmlInvoiceFIlePath and $HtmlInvoiceFilePath.
+.PARAMETER TemplateXmlFilePath
+ The file path to where the XML data used to prepopulate the invoice is
+.PARAMETER TemplateXslFilePath
+ The file path to where the XSL styling sheet is located
+.PARAMETER InvoiceFilePath
+ The file that will be output containing your invoice data
+.LINK
+ http://www.psclistens.com/blog/2014/3/powershell-tip-convert-html-to-pdf.aspx
+ http://pdfgenerator.codeplex.com
+#>
+[CmdletBinding()]
+[OutputType()]
+param (
+ [Parameter(Mandatory)]
+ [string]$InvoiceTitle,
+ [Parameter(Mandatory)]
+ [string]$InvoiceNumber,
+ [Parameter(Mandatory)]
+ [string]$ClientCompany,
+ [Parameter(Mandatory)]
+ [string]$ClientName,
+ [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
+ [hashtable[]]$Item,
+ [Parameter()]
+ [string]$TopNote,
+ [Parameter()]
+ [string]$BottomNote,
+ [Parameter()]
+ [switch]$Force,
+ [Parameter()]
+ [ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
+ [string]$TemplateXmlFilePath = "$PSScriptRoot\InvoiceTemplate.xml",
+ [Parameter()]
+ [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
+ [string]$TemplateXslFilePath = "$PSScriptRoot\InvoiceTemplate.xsl",
+ [Parameter()]
+ [string]$InvoiceFilePath = "$PSScriptRoot\Invoice.pdf"
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ ## Check to ensure an invoice doesn't already exist if Force was not chosen
+ if (!$Force.IsPresent) {
+ if (Test-Path -Path $InvoiceFilePath -PathType Leaf) {
+ throw 'Existing invoice already exists at specified path. To overwrite, use the -Force parameter'
+ }
+ }
+
+ function New-InvoiceItem ($ItemDescription, $ItemPrice, $ItemQuantity) {
+ ## Adds a new item to the invoice
+ $NewItem = $InvoiceXml.CreateElement("Item")
+ [void]$NewItem.AppendChild($InvoiceXml.CreateElement("Description"))
+ [void]$NewItem.AppendChild($InvoiceXml.CreateElement("Price"))
+ [void]$NewItem.AppendChild($InvoiceXml.CreateElement("Quantity"))
+ $NewItem.Description = $ItemDescription
+ $NewItem.Price = $ItemPrice
+ $NewItem.Quantity = $ItemQuantity
+ [void]$InvoiceXml.Invoice.AppendChild($NewItem)
+ }
+
+ Function ConvertTo-PDF {
+ [CmdletBinding()]
+ param(
+ [Parameter(ValueFromPipeline)]
+ [string]$Html,
+ [Parameter()]
+ [string]$FileName
+ )
+ begin {
+ $DllLoaded = $false
+ $PdfGenerator = "$PsScriptRoot\NReco.PdfGenerator.dll"
+ if (Test-Path $PdfGenerator) {
+ try {
+ $Assembly = [Reflection.Assembly]::LoadFrom($PdfGenerator)
+ $PdfCreator = New-Object NReco.PdfGenerator.HtmlToPdfConverter
+ $DllLoaded = $true
+ } catch {
+ Write-Error ('ConvertTo-PDF: Issue loading or using NReco.PdfGenerator.dll: {0}' -f $_.Exception.Message)
+ }
+ } else {
+ Write-Error ('ConvertTo-PDF: NReco.PdfGenerator.dll was not found.')
+ }
+ }
+ PROCESS {
+ if ($DllLoaded) {
+ $ReportOutput = $PdfCreator.GeneratePdf([string]$HTML)
+ Add-Content -Value $ReportOutput -Encoding byte -Path $FileName
+ } else {
+ Throw 'Error Occurred'
+ }
+ }
+ END { }
+ }
+
+
+} catch {
+ Write-Error $_.Exception.Message
+ break
+}
+}
+
+process {
+ try {
+ Write-Verbose 'Building XML object'
+ ## Build the initial XML object with default values from template XML
+ Write-Verbose "Getting template XML file $TemplateXmlFilePath"
+ $script:InvoiceXml = [xml](Get-Content $TemplateXmlFilePath)
+
+ ## Add per-invoice specific attributes to the XML object
+ $InvoiceXml.Invoice.InvoiceTitle = $InvoiceTitle
+ $InvoiceXml.Invoice.Date = (Get-Date).ToShortDateString()
+ $InvoiceXml.Invoice.ClientCompany = $ClientCompany
+ $InvoiceXml.Invoice.ClientName = $ClientName
+ $InvoiceXml.Invoice.InvoiceNumber = $InvoiceNumber
+ $InvoiceXml.Invoice.TopNote = $TopNote
+ $InvoiceXml.Invoice.BottomNote = $BottomNote
+
+ ## Add each item to the invoice XML object
+ foreach ($i in $Item) {
+ if (!($i.ContainsKey('Description')) -or !($i.ContainsKey('Price')) -or !($i.ContainsKey('Quantity'))) {
+ Write-Warning "Item found that does not have all necessary fields to add to invoice"
+ } else {
+ Write-Verbose "Adding invoice item '$($i.Description)' to invoice"
+ New-InvoiceItem -ItemDescription $i.Description -ItemPrice $i.Price -ItemQuantity $i.Quantity
+ }
+ }
+
+ ## Remove any existing invoices if they exist
+ Write-Verbose "Removing existing invoice '$InvoiceFilePath'"
+ Remove-Item -Path $InvoiceFilePath -Force -ea 'SilentlyContinue'
+
+ ## Save the invoice XML object to file
+ if (([System.IO.FileInfo]$InvoiceFilePath).Extension -eq '.xml') {
+ $XmlOutput = $InvoiceFilePath
+ } else {
+ $XmlOutput = "$PsScriptRoot\TempXml.xml"
+ }
+ Write-Verbose "Saving XML file output as '$XmlOutput'"
+ $InvoiceXml.Save($XmlOutput)
+
+ Write-Verbose "Loading XSL file '$TemplateXslFilePath'"
+ $xslt = New-Object System.Xml.Xsl.XslCompiledTransform
+ $xslt.Load($TemplateXslFilePath)
+
+ if (([System.IO.FileInfo]$InvoiceFilePath).Extension -eq '.html') {
+ $HtmlOutput = $InvoiceFilePath
+ } else {
+ $HtmlOutput = "$PsScriptRoot\TempHtml.Html"
+ }
+ Write-Verbose "Transforming XML to HTML file '$HtmlOutput'"
+ $xslt.Transform($XmlOutput, $HtmlOutput)
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+end {
+ try {
+ switch (([System.IO.FileInfo]$InvoiceFilePath).Extension) {
+ '.xml' {
+ Write-Verbose "XML output chosen. Removing temporary HTML file '$HtmlOutput'"
+ Remove-Item -Path $HtmlOutput -Force -ea 'SilentlyContinue'
+ }
+ '.html' {
+ Write-Verbose "HTML output chosen. Removing temporary XML file '$XmlOutput'"
+ Remove-Item -Path $XmlOutput -Force -ea 'SilentlyContinue'
+ }
+ '.pdf' {
+ Write-Verbose "Converting '$HtmlOutput' content to PDF file '$InvoiceFilePath'"
+ ConvertTo-PDF -Html (Get-Content $HtmlOutput -Raw) -FileName $InvoiceFilePath
+ Write-Verbose "PDF output chosen. Removing temporary HTML and XML files"
+ Remove-Item -Path $HtmlOutput -Force -ea 'SilentlyContinue'
+ Remove-Item -Path $XmlOutput -Force -ea 'SilentlyContinue'
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/New-Invoice/TempHtml.Html b/New-Invoice/TempHtml.Html
new file mode 100644
index 0000000..75cb48c
--- /dev/null
+++ b/New-Invoice/TempHtml.Html
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/New-Invoice/TempXml.xml b/New-Invoice/TempXml.xml
new file mode 100644
index 0000000..f9ceedc
--- /dev/null
+++ b/New-Invoice/TempXml.xml
@@ -0,0 +1,23 @@
+
+
+ February Articles
+ Adam Bertram
+ 8616 Elizabeth Ln
+ Evansville, IN
+ 47725
+ adbertram@gmail.com
+ 812-453-3658
+ 1105 Media
+ Chris Paoli
+ 02242015
+ 2/24/2015
+
+
+
+
+ -
+ Article
+ 200
+ 3
+
+
\ No newline at end of file
diff --git a/New-ScheduledScript.ps1 b/New-ScheduledScript.ps1
new file mode 100644
index 0000000..36cfdcb
--- /dev/null
+++ b/New-ScheduledScript.ps1
@@ -0,0 +1,180 @@
+<#
+.SYNOPSIS
+ This script creates a scheduled task on a local or remote system to execute a Powershell script
+ based on a number of criteria. It is useful if you have a central server that you create multiple
+ different scheduled tasks on.
+.NOTES
+ Created on: 1/20/15
+ Created by: Adam Bertram
+ Filename: New-ScheduledScript.ps1
+ Credits: http://blogs.technet.com/b/heyscriptingguy/archive/2015/01/16/use-powershell-to-create-scheduled-task-in-new-folder.aspx
+.EXAMPLE
+ PS> New-ScheduledScript.ps1 -ScriptFilePath C:\callfromvbs.ps1 -LocalScriptFolderPath 'C:\' -TaskTriggerOptions @{'Daily' = $true; 'At' = '3Am'} -TaskName 'Test' -TaskRunAsUser 'lab.local\administrator' -TaskRunAsPassword 'mypassword' -Computername labdc.lab.local
+
+ This script would copy the C:\callfromvbs.ps1 to the C:\ of labdc.lab.local. It would then create a scheduled task on labdc.lab.local
+ named Test that ran daily at 3AM under the lab.local\administrator credentials.
+.PARAMETER ScriptFilePath
+ The remote or local file path of the Powershell script
+.PARAMETER ScriptParameters
+ Any parameters to execute with the script
+.PARAMETER LocalScriptFolderPath
+ If this script is copying a Powershell script from somewhere else, this is the folder path where the
+ script will be copied to and referenced to run in the scheduled task.
+.PARAMETER TaskTriggerOptions
+ A hashtable of parameters that will be passed to the New-ScheduledTaskTrigger cmdlet. For available options, visit
+ http://technet.microsoft.com/en-us/library/jj649821.aspx.
+.PARAMETER TaskName
+ The name of the scheduled task
+.PARAMETER TaskRunAsUser
+ The username that the task will run under
+.PARAMETER TaskRunAsPassword
+ The password to the runas user
+.PARAMETER Computername
+ The name of the system in which the scheduled task will be created (if not the local system)
+.PARAMETER PowershellRunAsArch
+ If your task scheduler machine is 64 bit this enforces the script to run under the 32 bit or 64 bit
+ Powershell host. By default, it will always run as 64 bit.
+#>
+[CmdletBinding()]
+[OutputType([bool])]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidatePattern('.*\.ps1$')]
+ [string]$ScriptFilePath,
+ [string]$ScriptParameters,
+ [Parameter(Mandatory)]
+ [string]$LocalScriptFolderPath,
+ [Parameter(Mandatory)]
+ [hashtable]$TaskTriggerOptions,
+ [Parameter(Mandatory)]
+ [string]$TaskName,
+ [Parameter(Mandatory)]
+ [string]$TaskRunAsUser,
+ [Parameter(Mandatory)]
+ [string]$TaskRunAsPassword,
+ [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1})]
+ [string]$Computername = 'localhost',
+ [ValidateSet('x86', 'x64')]
+ [string]$PowershellRunAsArch
+)
+
+begin {
+ $ErrorActionPreference = 'Stop'
+ Set-StrictMode -Version Latest
+ ## http://www.leeholmes.com/blog/2009/11/20/testing-for-powershell-remoting-test-psremoting/
+ function Test-PsRemoting {
+ param (
+ [Parameter(Mandatory)]
+ $computername
+ )
+
+ try {
+ $errorActionPreference = "Stop"
+ $result = Invoke-Command -ComputerName $computername { 1 }
+ } catch {
+ return $false
+ }
+
+ ## I’ve never seen this happen, but if you want to be
+ ## thorough….
+ if ($result -ne 1) {
+ Write-Verbose "Remoting to $computerName returned an unexpected result."
+ return $false
+ }
+ $true
+ }
+
+ function Get-ComputerArchitecture {
+ if ((Get-CimInstance -ComputerName $Computername Win32_ComputerSystem -Property SystemType).SystemType -eq 'x64-based PC') {
+ 'x64'
+ } else {
+ 'x86'
+ }
+ }
+
+ function Get-PowershellFilePath {
+ ## If user wants to run the script under the x86 host and the machine is x64
+ if (($PowershellRunAsArch -eq 'x86') -and ((Get-ComputerArchitecture) -eq 'x64')) {
+ if ($Computername -eq 'localhost') {
+ ## If it's the localhost no need to do a WinRM query
+ "$($PsHome.Replace('System32','SysWow64'))\powershell.exe"
+ } else {
+ Invoke-Command -ComputerName $Computername -ScriptBlock { "$($PsHome.Replace('System32','SysWow64'))\powershell.exe" }
+ }
+ } else {
+ ## If the machine is 32 bit anyway or if it's 64 bit and the user wants 64 bit just use $PsHome
+ if ($Computername -eq 'localhost') {
+ ## If it's the localhost no need to do a WinRM query
+ "$PsHome\powershell.exe"
+ } else {
+ Invoke-Command -ComputerName $Computername -ScriptBlock { "$PsHome\powershell.exe" }
+ }
+ }
+ }
+
+ function New-MyScheduledTask {
+ try {
+ $PowershellFilePath = Get-PowershellFilePath
+ if ($Computername -ne 'localhost') {
+ $ScriptBlock = {
+ $Action = New-ScheduledTaskAction -Execute $using:PowershellFilePath -Argument "-NonInteractive -NoLogo -NoProfile -File $using:ScriptFilePath $using:ScriptParameters"
+ $TaskTriggerOptions = $using:TaskTriggerOptions
+ $Trigger = New-ScheduledTaskTrigger @TaskTriggerOptions
+ $RunAsUser = $using:TaskRunAsUser
+ $Task = New-ScheduledTask -Action $Action -Trigger $Trigger -Settings (New-ScheduledTaskSettingsSet);
+ Register-ScheduledTask -TaskName $using:TaskName -InputObject $Task -User $using:TaskRunAsUser -Password $using:TaskRunAsPassword
+ }
+ $Params = @{
+ 'Scriptblock' = $ScriptBlock
+ 'Computername' = $Computername
+ }
+ Invoke-Command @Params
+ } else {
+ $Action = New-ScheduledTaskAction -Execute $PowershellFilePath -Argument "-NonInteractive -NoLogo -NoProfile -File $ScriptFilePath"
+ $Trigger = New-ScheduledTaskTrigger @TaskTriggerOptions
+ $Task = New-ScheduledTask -Action $Action -Trigger $Trigger -Settings (New-ScheduledTaskSettingsSet);
+ Register-ScheduledTask -TaskName $TaskName -InputObject $Task -User $TaskRunAsUser -Password $TaskRunAsPassword
+ }
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ $false
+ }
+ }
+
+ function Get-UncPath ($HostName,$LocalPath) {
+ $NewPath = $LocalPath -replace (":", "$")
+ if ($NewPath.EndsWith("\")) {
+ $NewPath = [Regex]::Replace($NewPath, "\\$", "")
+ }
+ "\\$HostName\$NewPath"
+ }
+
+ try {
+ if (($Computername -ne 'localhost') -and !(Test-PsRemoting -computername $Computername)) {
+ throw "PS remoting not available on the computer $Computername"
+ }
+ ## Ensure there's no trailing slash
+ $LocalScriptFolderPath = $LocalScriptFolderPath.TrimEnd('\')
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ exit
+ }
+}
+
+process {
+ try {
+ ## If we're not creating a task on the local computer or if the script to execute is not on the local computer
+ ## copy to the task scheduler system and change the script to execute to a local path
+ if ($ScriptFilePath.StartsWith('\\') -or ($Computername -ne 'localhost')) {
+ Copy-Item -Path $ScriptFilePath -Destination (Get-UncPath -HostName $Computername -LocalPath $LocalScriptFolderPath)
+ $ScriptFilePath = "$LocalScriptFolderPath\$($ScriptFilePath | Split-Path -Leaf)"
+ }
+ New-MyScheduledTask | Out-Null
+ $true
+ } catch {
+ Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ $false
+ }
+}
\ No newline at end of file
diff --git a/New-ValidationDynamicParam.ps1 b/New-ValidationDynamicParam.ps1
new file mode 100644
index 0000000..5cad4eb
--- /dev/null
+++ b/New-ValidationDynamicParam.ps1
@@ -0,0 +1,90 @@
+function Set-Example {
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory)]
+ [ValidateScript({ Test-Path -Path $_ })]
+ [string]$Path,
+ [Parameter(Mandatory)]
+ [string]$Identity
+ )
+ DynamicParam {
+ $ParamOptions = @(
+ @{
+ 'Name' = 'Right';
+ 'Mandatory' = $true;
+ 'ValidateSetOptions' = ([System.Security.AccessControl.FileSystemRights]).DeclaredMembers | where { $_.IsStatic } | select -ExpandProperty name
+ },
+ @{
+ 'Name' = 'InheritanceFlags';
+ 'Mandatory' = $true;
+ 'ValidateSetOptions' = ([System.Security.AccessControl.InheritanceFlags]).DeclaredMembers | where { $_.IsStatic } | select -ExpandProperty name
+ },
+ @{
+ 'Name' = 'PropagationFlags';
+ 'Mandatory' = $true;
+ 'ValidateSetOptions' = ([System.Security.AccessControl.PropagationFlags]).DeclaredMembers | where { $_.IsStatic } | select -ExpandProperty name
+ },
+ @{
+ 'Name' = 'Type';
+ 'Mandatory' = $true;
+ 'ValidateSetOptions' = ([System.Security.AccessControl.AccessControlType]).DeclaredMembers | where { $_.IsStatic } | select -ExpandProperty name
+ }
+ )
+ $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
+ foreach ($Param in $ParamOptions) {
+ $RuntimeParam = New-ValidationDynamicParam @Param
+ $RuntimeParamDic.Add($Param.Name, $RuntimeParam)
+ }
+
+ return $RuntimeParamDic
+ }
+
+ begin {
+ $PsBoundParameters.GetEnumerator() | foreach { New-Variable -Name $_.Key -Value $_.Value -ea 'SilentlyContinue' }
+ }
+
+ process {
+ try {
+ $Acl = Get-Acl $Path
+ #$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule('Everyone', 'FullControl', 'ContainerInherit,ObjectInherit', 'NoPropagateInherit', 'Allow')
+ $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($Identity, $Right, $InheritanceFlags, $PropagationFlags, $Type)
+ $Acl.SetAccessRule($Ar)
+ Set-Acl $Path $Acl
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
+
+function New-ValidationDynamicParam {
+ [CmdletBinding()]
+ [OutputType('System.Management.Automation.RuntimeDefinedParameter')]
+ param (
+ [Parameter(Mandatory)]
+ [ValidateNotNullOrEmpty()]
+ [string]$Name,
+ [ValidateNotNullOrEmpty()]
+ [Parameter(Mandatory)]
+ [array]$ValidateSetOptions,
+ [Parameter()]
+ [switch]$Mandatory = $false,
+ [Parameter()]
+ [string]$ParameterSetName = '__AllParameterSets',
+ [Parameter()]
+ [switch]$ValueFromPipeline = $false,
+ [Parameter()]
+ [switch]$ValueFromPipelineByPropertyName = $false
+ )
+
+ $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
+ $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
+ $ParamAttrib.Mandatory = $Mandatory.IsPresent
+ $ParamAttrib.ParameterSetName = $ParameterSetName
+ $ParamAttrib.ValueFromPipeline = $ValueFromPipeline.IsPresent
+ $ParamAttrib.ValueFromPipelineByPropertyName = $ValueFromPipelineByPropertyName.IsPresent
+ $AttribColl.Add($ParamAttrib)
+ $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($ValidateSetOptions)))
+ $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Name, [string], $AttribColl)
+ $RuntimeParam
+
+}
\ No newline at end of file
diff --git a/PacketCapture.ps1 b/PacketCapture.ps1
new file mode 100644
index 0000000..156c791
--- /dev/null
+++ b/PacketCapture.ps1
@@ -0,0 +1,112 @@
+#region function Start-PacketTrace
+function Start-PacketTrace {
+<#
+ .SYNOPSIS
+ This function starts a packet trace using netsh. Upon completion, it will begin capture all
+ packets coming into and leaving the local computer and will continue to do do until
+ Stop-PacketCapture is executed.
+ .EXAMPLE
+ PS> Start-PacketTrace -TraceFilePath C:\Tracefile.etl
+
+ This example will begin a packet capture on the local computer and place all activity
+ in the ETL file C:\Tracefile.etl.
+
+ .PARAMETER TraceFilePath
+ The file path where the trace file will be placed and recorded to. This file must be an ETL file.
+
+ .PARAMETER Force
+ Use the Force parameter to overwrite the trace file if one exists already
+
+ .INPUTS
+ None. You cannot pipe objects to Start-PacketTrace.
+
+ .OUTPUTS
+ None. Start-PacketTrace returns no output upon success.
+#>
+ [CmdletBinding()]
+ [OutputType()]
+ param
+ (
+ [Parameter()]
+ [ValidateNotNullOrEmpty()]
+ [ValidateScript({ Test-Path -Path ($_ | Split-Path -Parent) -PathType Container })]
+ [ValidatePattern('.*\.etl$')]
+ [string[]]$TraceFilePath,
+ [Parameter()]
+ [switch]$Force
+ )
+ begin {
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ }
+ process {
+ try {
+ if (Test-Path -Path $TraceFilePath -PathType Leaf) {
+ if (-not ($Force.IsPresent)) {
+ throw "An existing trace file was found at [$($TraceFilePath)] and -Force was not used. Exiting.."
+ } else {
+ Remove-Item -Path $TraceFilePath
+ }
+ }
+ $OutFile = "$PSScriptRoot\temp.txt"
+ $Process = Start-Process "$($env:windir)\System32\netsh.exe" -ArgumentList "trace start persistent=yes capture=yes tracefile=$TraceFilePath" -RedirectStandardOutput $OutFile -Wait -NoNewWindow -PassThru
+ if ($Process.ExitCode -notin @(0, 3010)) {
+ throw "Failed to start the packet trace. Netsh exited with an exit code [$($Process.ExitCode)]"
+ } else {
+ Write-Verbose -Message "Successfully started netsh packet capture. Capturing all activity to [$($TraceFilePath)]"
+ }
+ } catch {
+ Write-Error -Message "$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ } finally {
+ if (Test-Path -Path $OutFile -PathType Leaf) {
+ Remove-Item -Path $OutFile
+ }
+ }
+ }
+}
+#endregion function Start-PacketTrace
+
+#region function Stop-PacketTrace
+function Stop-PacketTrace {
+<#
+ .SYNOPSIS
+ This function stops a packet trace that is currently running using netsh.
+ .EXAMPLE
+ PS> Stop-PacketTrace
+
+ This example stops any running netsh packet capture.
+ .INPUTS
+ None. You cannot pipe objects to Stop-PacketTrace.
+
+ .OUTPUTS
+ None. Stop-PacketTrace returns no output upon success.
+#>
+ [CmdletBinding()]
+ [OutputType()]
+ param
+ ()
+ begin {
+ Set-StrictMode -Version Latest
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ }
+ process {
+ try {
+ $OutFile = "$PSScriptRoot\temp.txt"
+ $Process = Start-Process "$($env:windir)\System32\netsh.exe" -ArgumentList 'trace stop' -Wait -NoNewWindow -PassThru -RedirectStandardOutput $OutFile
+ if ((Get-Content $OutFile) -eq 'There is no trace session currently in progress.'){
+ Write-Verbose -Message 'There are no trace sessions currently in progress'
+ } elseif ($Process.ExitCode -notin @(0, 3010)) {
+ throw "Failed to stop the packet trace. Netsh exited with an exit code [$($Process.ExitCode)]"
+ } else {
+ Write-Verbose -Message 'Successfully stopped netsh packet capture'
+ }
+ } catch {
+ Write-Error -Message "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ } finally {
+ if (Test-Path -Path $OutFile -PathType Leaf) {
+ Remove-Item -Path $OutFile
+ }
+ }
+ }
+}
+#endregion function Stop-PacketTrace
\ No newline at end of file
diff --git a/PostMessageToCampfire.ps1 b/PostMessageToCampfire.ps1
new file mode 100644
index 0000000..d5189ff
--- /dev/null
+++ b/PostMessageToCampfire.ps1
@@ -0,0 +1,10 @@
+$RoomNumber = '407559'
+$postUrl = "https://adambertram.campfirenow.com/room/$RoomNumber/speak.json"
+$body = 'test message'
+$token = '25f1733faf39d63ac8a860430fc18448df179b28'
+
+$message = 'TextMessage'+$body+''
+$baseuri = $base_url + '/speak.xml'
+$contentType = 'application/xml'
+$headers = @{"Authorization" = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($token+": x"))}
+Invoke-WebRequest -Uri $postUrl -Headers $headers -Method Post -body $message -contenttype 'application/xml'
\ No newline at end of file
diff --git a/Remove-AutoConfigProxy.ps1 b/Remove-AutoConfigProxy.ps1
new file mode 100644
index 0000000..42e0a84
--- /dev/null
+++ b/Remove-AutoConfigProxy.ps1
@@ -0,0 +1,200 @@
+<#
+.SYNOPSIS
+ This script nullifies the AutoConfigURL registry value in the HKCU hive for the current and all
+ other users. This removes the auto configuration proxy URL value in IE.
+.NOTES
+ Created on: 5/7/2015
+ Created by: Adam Bertram
+ Filename: Remove-AutoConfigProxy.ps1
+.EXAMPLE
+ PS> .\Remove-AutoConfigProxy.ps1
+
+ This nullifies the AutoConfigURL HKCU registry value for the current and all other user profiles to
+ remove the IE auto configuration URL.
+
+#>
+[CmdletBinding()]
+param ()
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+
+ function Get-LoggedOnUserSID {
+ <#
+ .SYNOPSIS
+ This function queries the registry to find the SID of the user that's currently logged onto the computer interactively.
+ #>
+ [CmdletBinding()]
+ param ()
+ process {
+ try {
+ if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {
+ New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null
+ }
+ (Get-ChildItem HKU: | where { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' }).PSChildName
+ } catch {
+ Write-Warning -Message "$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+ }
+ }
+
+ function Set-RegistryValueForAllUsers {
+ <#
+ .SYNOPSIS
+ This function sets a registry value in every user profile hive.
+ .EXAMPLE
+ PS> Set-RegistryValueForAllUsers -RegistryInstance @{'Name' = 'Setting'; 'Type' = 'String'; 'Value' = 'someval'; 'Path' = 'SOFTWARE\Microsoft\Windows\Something'}
+
+ This example would modify the string registry value 'Type' in the path 'SOFTWARE\Microsoft\Windows\Something' to 'someval'
+ for every user registry hive.
+ .PARAMETER RegistryInstance
+ A hash table containing key names of 'Name' designating the registry value name, 'Type' to designate the type
+ of registry value which can be 'String,Binary,Dword,ExpandString or MultiString', 'Value' which is the value itself of the
+ registry value and 'Path' designating the parent registry key the registry value is in.
+ .PARAMETER Remove
+ A switch parameter that overrides the default setting to only change or add registry values. This removes one of more registry keys instead.
+ If this parameter is used the only required key in the RegistryInstance parameter is Path. This will automatically remove both
+ the x86 and x64 paths if the key is a child under the SOFTWARE key. There's no need to specify the WOW6432Node path also.
+ .PARAMETER Force
+ A switch parameter that is used if the registry value key path doesn't exist will create the entire parent/child key hierachy and creates the
+ registry value. If this parameter is not used, if the key the value is supposed to be in does not exist the function will skip the value.
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [hashtable[]]$RegistryInstance,
+ [switch]$Remove
+ )
+ begin {
+ if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {
+ New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null
+ }
+ }
+ process {
+ try {
+ ## Change the registry values for the currently logged on user
+ $LoggedOnSids = Get-LoggedOnUserSID
+ if ($LoggedOnSids -is [string]) {
+ Write-Verbose -Message "Found 1 logged on user SID"
+ } else {
+ Write-Verbose -Message "Found $($LoggedOnSids.Count) logged on user SIDs"
+ }
+ foreach ($sid in $LoggedOnSids) {
+ Write-Verbose -Message "Loading the user registry hive for the logged on SID $sid"
+ foreach ($instance in $RegistryInstance) {
+ if ($Remove.IsPresent) {
+ Write-Verbose -Message "Removing registry key '$($instance.path)'"
+ Remove-Item -Path "HKU:\$sid\$($instance.Path)" -Recurse -Force -ea 'SilentlyContinue'
+ } else {
+ if (!(Get-Item -Path "HKU:\$sid\$($instance.Path)" -ea 'SilentlyContinue')) {
+ Write-Verbose -Message "The registry key HKU:\$sid\$($instance.Path) does not exist. Creating..."
+ New-Item -Path "HKU:\$sid\$($instance.Path | Split-Path -Parent)" -Name ($instance.Path | Split-Path -Leaf) -Force | Out-Null
+ } else {
+ Write-Verbose -Message "The registry key HKU:\$sid\$($instance.Path) already exists. No need to create."
+ }
+ Write-Verbose -Message "Setting registry value $($instance.Name) at path HKU:\$sid\$($instance.Path) to $($instance.Value)"
+
+ Set-ItemProperty -Path "HKU:\$sid\$($instance.Path)" -Name $instance.Name -Value $instance.Value -Type $instance.Type -Force
+ }
+ }
+ }
+
+ foreach ($instance in $RegistryInstance) {
+ if ($Remove.IsPresent) {
+ if ($instance.Path.Split('\')[0] -eq 'SOFTWARE' -and ((Get-Architecture) -eq 'x64')) {
+ $Split = $instance.Path.Split('\')
+ $x86Path = "HKCU\SOFTWARE\Wow6432Node\{0}" -f ($Split[1..($Split.Length)] -join '\')
+ $CommandLine = "reg delete `"{0}`" /f && reg delete `"{1}`" /f" -f "HKCU\$($instance.Path)", $x86Path
+ } else {
+ $CommandLine = "reg delete `"{0}`" /f" -f "HKCU\$($instance.Path)"
+ }
+ } else {
+ ## Convert the registry value type to one that reg.exe can understand
+ switch ($instance.Type) {
+ 'String' {
+ $RegValueType = 'REG_SZ'
+ }
+ 'Dword' {
+ $RegValueType = 'REG_DWORD'
+ }
+ 'Binary' {
+ $RegValueType = 'REG_BINARY'
+ }
+ 'ExpandString' {
+ $RegValueType = 'REG_EXPAND_SZ'
+ }
+ 'MultiString' {
+ $RegValueType = 'REG_MULTI_SZ'
+ }
+ default {
+ throw "Registry type '$($instance.Type)' not recognized"
+ }
+ }
+ if (!(Get-Item -Path "HKCU:\$($instance.Path)" -ea 'SilentlyContinue')) {
+ Write-Verbose -Message "The registry key 'HKCU:\$($instance.Path)'' does not exist. Creating..."
+ New-Item -Path "HKCU:\$($instance.Path) | Split-Path -Parent)" -Name ("HKCU:\$($instance.Path)" | Split-Path -Leaf) -Force | Out-Null
+ }
+ if (-not $instance.Value) {
+ $instance.Value = '""'
+ }
+ $CommandLine = "reg add `"{0}`" /v {1} /t {2} /d {3} /f" -f "HKCU\$($instance.Path)", $instance.Name, $RegValueType, $instance.Value
+ }
+ Set-AllUserStartupAction -CommandLine $CommandLine
+ }
+ } catch {
+ Write-Warning -Message "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ $false
+ }
+ }
+ }
+
+ function Set-AllUserStartupAction {
+ <#
+ .SYNOPSIS
+ A function that executes a command line for the any current logged on user and uses the Active Setup registry key to set a
+ registry value that contains a command line EXE with arguments that will be executed once for every user that logs in.
+ .PARAMETER CommandLine
+ The command line string that will be executed once at every user logon
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [string]$CommandLine
+ )
+ process {
+ try {
+ ## Create the Active Setup registry key so that the reg add cmd will get ran for each user
+ ## logging into the machine.
+ ## http://www.itninja.com/blog/view/an-active-setup-primer
+ $Guid = [guid]::NewGuid().Guid
+ Write-Verbose -Message "Created GUID '$Guid' to use for Active Setup"
+ $ActiveSetupRegParentPath = 'HKLM:\Software\Microsoft\Active Setup\Installed Components'
+ New-Item -Path $ActiveSetupRegParentPath -Name $Guid -Force | Out-Null
+ $ActiveSetupRegPath = "HKLM:\Software\Microsoft\Active Setup\Installed Components\$Guid"
+ Write-Verbose -Message "Using registry path '$ActiveSetupRegPath'"
+ Write-Verbose -Message "Setting command line registry value to '$CommandLine'"
+ Set-ItemProperty -Path $ActiveSetupRegPath -Name '(Default)' -Value 'All Users Startup Action' -Force
+ Set-ItemProperty -Path $ActiveSetupRegPath -Name 'Version' -Value '1' -Force
+ Set-ItemProperty -Path $ActiveSetupRegPath -Name 'StubPath' -Value $CommandLine -Force
+ Write-Verbose -Message 'Done'
+ } catch {
+ Write-Warning -Message "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ $false
+ }
+ }
+ }
+}
+
+process {
+ try {
+ $Instance = @{
+ 'Name' = 'AutoConfigURL';
+ 'Type' = 'String';
+ 'Path' = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings';
+ 'Value' = ''
+ }
+ Set-RegistryValueForAllUsers -RegistryInstance $Instance
+ } catch {
+ Write-Error "$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+}
\ No newline at end of file
diff --git a/Remove-CMDirectMembershipRule.ps1 b/Remove-CMDirectMembershipRule.ps1
new file mode 100644
index 0000000..ac893c9
--- /dev/null
+++ b/Remove-CMDirectMembershipRule.ps1
@@ -0,0 +1,84 @@
+<#
+.NOTES
+ Created on: 5/22/2014 10:56 AM
+ Created by: Adam Bertram
+ Filename: Remove-CMDirectMembershipRule.ps1
+ Credits: http://www.david-obrien.net/2013/02/24/remove-direct-membership-rules-configmgr/
+.DESCRIPTION
+ This script removes all direct membership rules from a specified collection name
+.EXAMPLE
+ .\Remove-CMDirectMembershipRule.ps1 -SiteCode 'CON' -SiteServer 'SERVERNAME' -CollectionName 'NAMEHERE'
+.PARAMETER SiteCode
+ Your Configuration Manager site code
+.PARAMETER SiteServer
+ Your Configuration Manager site server name
+.PARAMETER CollectionName
+ The collection name you'd like to remove direct membership rules from
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $True)]
+ [string]$SiteCode = 'UHP',
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $True)]
+ [string]$SiteServer = 'CONFIGMANAGER',
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $True,
+ ValueFromPipelineByPropertyName = $True)]
+ [string]$CollectionName
+)
+
+begin {
+ try {
+ if ([Environment]::Is64BitProcess) {
+ # this script needs to run in a x86 shell, but we need to access the x64 reg-hive to get the AdminConsole install directory
+ throw 'This script must be run in a x86 shell.'
+ }
+ $ConfigMgrModule = "$($env:SMS_ADMIN_UI_PATH | Split-Path -Parent)\ConfigurationManager.psd1"
+ if (!(Test-Path $ConfigMgrModule)) {
+ throw 'Configuration Manager module not found in admin console path'
+ }
+ Import-Module $ConfigMgrModule
+
+ $BeforeLocation = (Get-Location).Path
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+ Set-Location "$SiteCode`:"
+ $CommonWmiParams = @{
+ 'ComputerName' = $SiteServer
+ 'Namespace' = "root\sms\site_$SiteCode"
+ }
+ #HACK: This should be 1 WQL query using JOIN but it's not immediately obvious why that doesn't work
+ #Get-WmiObject -ComputerName $SiteServer -Namespace "ROOT\sms\site_$SiteCode" -Query "SELECT DISTINCT * FROM SMS_CollectionMember_a AS collmem JOIN SMS_Collection AS coll ON coll.CollectionID = collmem.CollectionID"
+
+ $CollectionId = Get-WmiObject @CommonWmiParams -Query "SELECT CollectionID FROM SMS_Collection WHERE Name = '$CollectionName'" | select -ExpandProperty CollectionID
+ if (!$CollectionId) {
+ throw "No collection found with the name $CollectionName"
+ }
+
+ ## Find the collection members
+ $CollectionMembers = Get-WmiObject @CommonWmiParams -Query "SELECT Name FROM SMS_CollectionMember_a WHERE CollectionID = '$CollectionId'" | Select -ExpandProperty Name
+
+ if (!$CollectionMembers) {
+ Write-Warning 'No collection members found in collection'
+ } else {
+ foreach ($member in $CollectionMembers) {
+ Remove-CMDeviceCollectionDirectMembershipRule -CollectionID $CollectionID -ResourceName $member -force
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+end {
+ Set-Location $BeforeLocation
+}
\ No newline at end of file
diff --git a/Restore-AzureVMSnapshot.ps1 b/Restore-AzureVMSnapshot.ps1
new file mode 100644
index 0000000..d4e983e
--- /dev/null
+++ b/Restore-AzureVMSnapshot.ps1
@@ -0,0 +1,145 @@
+<#
+.SYNOPSIS
+ Restores a blob copy (snapshot) of all disks used by an Azure VM
+.NOTES
+ Created on: 7/6/2014
+ Created by: Adam Bertram
+ Filename: Restore-AzureVmSnapshot.ps1
+ Credits: http://bit.ly/1r1umit
+ Requirements: Azure IaaS VM
+.EXAMPLE
+ Restore-AzureVmSnapshot -AzureVM CCM1 -ServiceName CLOUD
+ This example will create a blob copy of all disks in the VM CCM1, Service name CLOUD
+.EXAMPLE
+ Create-AzureVmSnapshot -AzureVM 'CCM1','CCM2' -Overwrite
+ This example creates blob copies for all disks in the VMs CCM1 and CCM2 using the default
+ service name parameter and if any existing copies are detected, automatically overwrite them.
+.PARAMETER AzureVM
+ The name of the Azure VM. Multiple VM names are supported.
+.PARAMETER ServiceName
+ The name of your Azure cloud service.
+.PARAMETER VmExportConfigFolderPath
+ The VM has to be removed in order to restore the snapshot. This is the directory on the local
+ computer that will house the export VM configuration XMLs to be used to restore the VM afterwards.
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline)]
+ [string[]]$AzureVM,
+ [string]$ServiceName = 'ADBCLOUD',
+ [string]$VmExportConfigFolderPath = 'C:\ExportedVMs'
+)
+
+begin {
+ Set-StrictMode -Version Latest
+ try {
+ ## Ensure the module is available and import it
+ $AzureModuleFilePath = "$($env:ProgramFiles)\Microsoft SDKs\Windows Azure\PowerShell\ServiceManagement\Azure\Azure.psd1"
+ if (!(Test-Path $AzureModuleFilePath)) {
+ Write-Error 'Azure module not found'
+ } else {
+ Import-Module $AzureModuleFilePath
+ }
+
+ $script:BackupContainer = 'backups'
+
+ ## Ensure a path to store the VM configurations to reprovision the VM later
+ if (!(Test-Path $VmExportConfigFolderPath -PathType Container)) {
+ New-Item -Path $VmExportConfigFolderPath -ItemType Directory | Out-Null
+ }
+
+ function Restore-Snapshot([Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMModel.OSVirtualHardDisk]$Disk) {
+ $DiskName = $Disk.DiskName
+ $DiskUris = $Disk.MediaLink
+ $StorageAccount = $DiskUris.Host.Split('.')[0]
+ $Blob = $Disk.MediaLink.Segments[-1]
+ $Container = $Disk.MediaLink.Segments[-2].TrimEnd('/')
+
+ While ((Get-AzureDisk -DiskName $DiskName).AttachedTo) {
+ Write-Verbose "Waiting for $DiskName to detach..."
+ Start-Sleep 5
+ }
+ Remove-AzureDisk -DiskName $DiskName -DeleteVHD
+
+ $BlobCopyParams = @{
+ 'SrcContainer' = $BackupContainer;
+ 'SrcBlob' = $Blob;
+ 'DestContainer' = $Container;
+ 'Force' = $true
+ }
+ Start-AzureStorageBlobCopy @BlobCopyParams
+ Get-AzureStorageBlobCopyState -Container $Container -Blob $Blob -WaitForComplete
+ Add-AzureDisk -DiskName $DiskName -MediaLocation $DiskUris.AbsoluteUri -OS 'Windows'
+ }
+
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+
+process {
+ try {
+ foreach ($Vm in $AzureVM) {
+ $Vm = Get-AzureVM -ServiceName $ServiceName -Name $Vm
+ if ($Vm.Status -ne 'StoppedVM') {
+ if ($Vm.Status -eq 'ReadyRole') {
+ Write-Verbose "VM $($Vm.Name) is started. Bringing down into a provisioned state"
+ ## Bring the VM down in a provisioned state
+ $Vm | Stop-AzureVm -StayProvisioned
+ } elseif ($Vm.Status -eq 'StoppedDeallocated') {
+ Write-Verbose "VM $($Vm.Name) is stopped but not in a provisioned state."
+ ## Bring up the VM and bring it back down in a provisioned state
+ Write-Verbose "Starting up VM $($Vm.Name)..."
+ $Vm | Start-AzureVm
+ while ((Get-AzureVm -ServiceName $ServiceName -Name $Vm.Name).Status -ne 'ReadyRole') {
+ sleep 5
+ Write-Verbose "Waiting on VM $($Vm.Name) to be in a ReadyRole state..."
+ }
+ Write-Verbose "VM $($Vm.Name) now up. Bringing down into a provisioned state..."
+ $Vm | Stop-AzureVm -StayProvisioned
+ }
+
+ }
+
+ $OsDisk = $Vm | Get-AzureOSDisk
+
+ ## Export the VM's configuration
+ $VmExportPath = "$VmExportConfigFolderPath\$($Vm.Name).xml"
+ $Vm | Export-AzureVM -Path $VmExportPath
+
+ ## Remove the VM
+ Remove-AzureVM -ServiceName $Vm.ServiceName -Name $Vm.Name
+
+ ## Restore a snapshot of OS disk
+ Restore-Snapshot -Disk $OsDisk
+
+ ## Restore snapshots of all data disks
+ $DataDisks = $Vm | Get-AzureDataDisk
+ if ($DataDisks) {
+ Write-Verbose "Data disks found on VM. Restoring..."
+ foreach ($DataDisk in $DataDisks) {
+ Restore-Snapshot -Disk $DataDisk
+ }
+ }
+
+ Import-AzureVM -Path $VmExportPath | New-AzureVM -ServiceName $Vm.ServiceName
+ While ((Get-AzureVM -Name $Vm.Name).Status -ne 'ReadyRole') {
+ Write-Verbose "Waiting for $($Vm.Name) to become available again..."
+ Start-Sleep 5
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+
+end {
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/SCOMMonitors/FileCountInDirectory.ps1 b/SCOMMonitors/FileCountInDirectory.ps1
new file mode 100644
index 0000000..ab23815
--- /dev/null
+++ b/SCOMMonitors/FileCountInDirectory.ps1
@@ -0,0 +1,23 @@
+param (
+ [string]$DirectoryName,
+ [int]$MaxFileCount
+)
+
+$API = New-Object -ComObject 'MOM.ScriptAPI'
+$PropertyBag = $API.CreatePropertyBag()
+
+try {
+ $FileCount = (Get-ChildItem -Path $DirectoryName | Where-Object { !$_.PsIsContainer }).Count
+ if ($FileCount -ge $MaxFileCount) {
+ $PropertyBag.AddValue('State', 'Critical')
+ $PropertyBag.Addvalue('Description', "There are $($FileCount - $MaxFileCount) more files than what should be in the directory $DirectoryName")
+ } else {
+ $PropertyBag.AddValue('State', 'Healthy')
+ $PropertyBag.AddValue('Description', "There are less than $MaxFileCount files accumulated in the directory $DirectoryName")
+ }
+ $PropertyBag
+} catch {
+ $PropertyBag.AddValue('State', 'Warning')
+ $PropertyBag.Addvalue('Description', $_.Exception.Message)
+ $PropertyBag
+}
diff --git a/SCOMMonitors/WindowsProcessExists.ps1 b/SCOMMonitors/WindowsProcessExists.ps1
new file mode 100644
index 0000000..2aa51d2
--- /dev/null
+++ b/SCOMMonitors/WindowsProcessExists.ps1
@@ -0,0 +1,21 @@
+param ([string]$ProcessName)
+
+$API = New-Object -ComObject 'MOM.ScriptAPI'
+$PropertyBag = $API.CreatePropertyBag()
+
+try {
+ $Process = Get-WmiObject -Class 'Win32_Process' -Filter "Name = '$ProcessName'"
+ if (!$Process) {
+ $PropertyBag.AddValue('State', 'Critical')
+ $PropertyBag.Addvalue('Description', "The process '$ProcessName' is not running.")
+ } else {
+ $PropertyBag.AddValue('State', 'Healthy')
+ $PropertyBag.AddValue('Description', "The process '$ProcessName' is running.")
+ }
+ $PropertyBag
+} catch {
+ $PropertyBag.AddValue('State', 'Warning')
+ $PropertyBag.Addvalue('Description', $_.Exception.Message)
+ $PropertyBag
+}
+
diff --git a/Send-TwitterDirectMessage.ps1 b/Send-TwitterDirectMessage.ps1
new file mode 100644
index 0000000..2a850cc
--- /dev/null
+++ b/Send-TwitterDirectMessage.ps1
@@ -0,0 +1,61 @@
+$VerbosePreference = 'continue'
+
+## Works with Twitter API v1.1
+[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
+[Reflection.Assembly]::LoadWithPartialName("System.Net") | Out-Null
+
+$status = [System.Uri]::EscapeDataString("test")
+$oauth_consumer_key = "qE0WtEleJzp8ErWI82B3Jw8gl"
+$oauth_consumer_secret = "r06813yKNEjaErJc4mkacWmFSNwnT1H05Yyjd8XQ5bOhKxVBVc"
+$oauth_token = "19891458-D4dvUVxHpHMCiTppg7OF4kPKgQQXM2O8rhXMFiNVc"
+$oauth_token_secret = "ZPLaltGH39jdYagotyQL5C9IYAKty6xovhBUcvPc5IYbr"
+$oauth_nonce = [System.Convert]::ToBase64String(([System.Text.Encoding]::ASCII.GetBytes("$([System.DateTime]::Now.Ticks.ToString())12345"))).Replace('=','g')
+Write-Verbose "Using oauth_nonce of $oauth_nonce"
+$ts = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null)
+$oauth_timestamp = [System.Convert]::ToInt64($ts.TotalSeconds).ToString();
+Write-Verbose "Oauth timestamp is $oauth_timestamp"
+
+## Keys must be in alphabetical order
+$signature = "POST&";
+$signature += [System.Uri]::EscapeDataString("https://api.twitter.com/1.1/statuses/update.json") + "&"
+$signature += [System.Uri]::EscapeDataString("oauth_consumer_key=" + $oauth_consumer_key + "&");
+$signature += [System.Uri]::EscapeDataString("oauth_nonce=" + $oauth_nonce + "&");
+$signature += [System.Uri]::EscapeDataString("oauth_signature_method=HMAC-SHA1&");
+$signature += [System.Uri]::EscapeDataString("oauth_timestamp=" + $oauth_timestamp + "&");
+$signature += [System.Uri]::EscapeDataString("oauth_token=" + $oauth_token + "&");
+$signature += [System.Uri]::EscapeDataString("oauth_version=1.0&");
+$signature += [System.Uri]::EscapeDataString("status=" + $status);
+Write-Verbose "Unencrypted signature = $signature"
+
+$signature_key = [System.Uri]::EscapeDataString($oauth_consumer_secret) + "&" + [System.Uri]::EscapeDataString($oauth_token_secret);
+
+$hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
+$hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($signature_key);
+$oauth_signature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($signature)));
+Write-Verbose "Using the signature '$oauth_signature'"
+
+$oauth_authorization = 'OAuth ';
+$oauth_authorization += 'oauth_consumer_key="' + [System.Uri]::EscapeDataString($oauth_consumer_key) + '", ';
+$oauth_authorization += 'oauth_nonce="' + [System.Uri]::EscapeDataString($oauth_nonce) + '", ';
+$oauth_authorization += 'oauth_signature="' + [System.Uri]::EscapeDataString($oauth_signature) + '", ';
+$oauth_authorization += 'oauth_signature_method="HMAC-SHA1",'
+$oauth_authorization += 'oauth_timestamp="' + [System.Uri]::EscapeDataString($oauth_timestamp) + '", '
+$oauth_authorization += 'oauth_token="' + [System.Uri]::EscapeDataString($oauth_token) + '", ';
+$oauth_authorization += 'oauth_version="1.0"';
+Write-Verbose "Authorization string is $oauth_authorization"
+
+$post_body = [System.Text.Encoding]::ASCII.GetBytes("status=" + $status);
+
+Invoke-RestMethod -URI 'https://api.twitter.com/1.1/statuses/update.json' -Method Post -Body $post_body -Headers @{ 'Authorization' = $oauth_authorization } -ContentType "application/x-www-form-urlencoded"
+
+<#
+[System.Net.HttpWebRequest] $request = [System.Net.WebRequest]::Create("https://api.twitter.com/1.1/statuses/update.json");
+$request.Method = "POST";
+$request.Headers.Add("Authorization", $oauth_authorization);
+$request.ContentType = "application/x-www-form-urlencoded";
+$body = $request.GetRequestStream();
+$body.write($post_body, 0, $post_body.length);
+$body.flush();
+$body.close();
+$response = $request.GetResponse();
+#>
\ No newline at end of file
diff --git a/Send-TwitterDirectMessagev2.ps1 b/Send-TwitterDirectMessagev2.ps1
new file mode 100644
index 0000000..6688fac
--- /dev/null
+++ b/Send-TwitterDirectMessagev2.ps1
@@ -0,0 +1,201 @@
+function Get-OAuthAuthorization {
+ <#
+ .SYNOPSIS
+ This function is used to setup all the appropriate security stuff needed to issue
+ API calls against Twitter's API. It has been tested with v1.1 of the API. It currently
+ includes support only for sending tweets from a single user account and to send DMs from
+ a single user account.
+ .EXAMPLE
+ Get-OAuthAuthorization -DmMessage 'hello' -HttpEndPoint 'https://api.twitter.com/1.1/direct_messages/new.json' -Username adam
+
+ This example gets the authorization string needed in the HTTP POST method to send a direct
+ message with the text 'hello' to the user 'adam'.
+ .EXAMPLE
+ Get-OAuthAuthorization -TweetMessage 'hello' -HttpEndPoint 'https://api.twitter.com/1.1/statuses/update.json'
+
+ This example gets the authorization string needed in the HTTP POST method to send out a tweet.
+ .PARAMETER HttpEndPoint
+ This is the URI that you must use to issue calls to the API.
+ .PARAMETER TweetMessage
+ Use this parameter if you're sending a tweet. This is the tweet's text.
+ .PARAMETER DmMessage
+ If you're sending a DM to someone, this is the DM's text.
+ .PARAMETER Username
+ If you're sending a DM to someone, this is the username you'll be sending to.
+ .PARAMETER ApiKey
+ The API key for the Twitter application you previously setup.
+ .PARAMETER ApiSecret
+ The API secret key for the Twitter application you previously setup.
+ .PARAMETER AccessToken
+ The access token that you generated within your Twitter application.
+ .PARAMETER
+ The access token secret that you generated within your Twitter application.
+ #>
+ [CmdletBinding(DefaultParameterSetName = 'None')]
+ [OutputType('System.Management.Automation.PSCustomObject')]
+ param (
+ [Parameter(Mandatory)]
+ [string]$HttpEndPoint,
+ [Parameter(Mandatory,ParameterSetName='NewTweet')]
+ [string]$TweetMessage,
+ [Parameter(Mandatory, ParameterSetName = 'DM')]
+ [string]$DmMessage,
+ [Parameter(Mandatory,ParameterSetName='DM')]
+ [string]$Username,
+ [Parameter()]
+ [string]$ApiKey = 'qE0WtEleJzp8ErWI82B3Jw8gl',
+ [Parameter()]
+ [string]$ApiSecret = 'r06813yKNEjaErJc4mkacWmFSNwnT1H05Yyjd8XQ5bOhKxVBVc',
+ [Parameter()]
+ [string]$AccessToken = '19891458-FL3362GmrreMWUfoPM9Y1gpluAigRbsS36Fv2t2d0',
+ [Parameter()]
+ [string]$AccessTokenSecret = 'muMHKAbmdIu1P462gZel4HhhnySGx6VwoaeN2cBVt8EEf'
+ )
+
+ begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
+ [Reflection.Assembly]::LoadWithPartialName("System.Net") | Out-Null
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+
+ process {
+ try {
+ ## Generate a random 32-byte string. I'm using the current time (in seconds) and appending 5 chars to the end to get to 32 bytes
+ ## Base64 allows for an '=' but Twitter does not. If this is found, replace it with some alphanumeric character
+ $OauthNonce = [System.Convert]::ToBase64String(([System.Text.Encoding]::ASCII.GetBytes("$([System.DateTime]::Now.Ticks.ToString())12345"))).Replace('=', 'g')
+ Write-Verbose "Generated Oauth none string '$OauthNonce'"
+
+ ## Find the total seconds since 1/1/1970 (epoch time)
+ $EpochTimeNow = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null)
+ Write-Verbose "Generated epoch time '$EpochTimeNow'"
+ $OauthTimestamp = [System.Convert]::ToInt64($EpochTimeNow.TotalSeconds).ToString();
+ Write-Verbose "Generated Oauth timestamp '$OauthTimestamp'"
+
+ ## Build the signature
+ $SignatureBase = "$([System.Uri]::EscapeDataString($HttpEndPoint))&"
+ $SignatureParams = @{
+ 'oauth_consumer_key' = $ApiKey;
+ 'oauth_nonce' = $OauthNonce;
+ 'oauth_signature_method' = 'HMAC-SHA1';
+ 'oauth_timestamp' = $OauthTimestamp;
+ 'oauth_token' = $AccessToken;
+ 'oauth_version' = '1.0';
+ }
+ if ($TweetMessage) {
+ $SignatureParams.status = $TweetMessage
+ } elseif ($DmMessage) {
+ $SignatureParams.screen_name = $Username
+ $SignatureParams.text = $DmMessage
+ }
+
+ ## Create a string called $SignatureBase that joins all URL encoded 'Key=Value' elements with a &
+ ## Remove the URL encoded & at the end and prepend the necessary 'POST&' verb to the front
+ $SignatureParams.GetEnumerator() | sort name | foreach { $SignatureBase += [System.Uri]::EscapeDataString("$($_.Key)=$($_.Value)&") }
+ $SignatureBase = $SignatureBase.TrimEnd('%26')
+ $SignatureBase = 'POST&' + $SignatureBase
+ Write-Verbose "Base signature generated '$SignatureBase'"
+
+ ## Create the hashed string from the base signature
+ $SignatureKey = [System.Uri]::EscapeDataString($ApiSecret) + "&" + [System.Uri]::EscapeDataString($AccessTokenSecret);
+
+ $hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
+ $hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($SignatureKey);
+ $OauthSignature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($SignatureBase)));
+ Write-Verbose "Using signature '$OauthSignature'"
+
+ ## Build the authorization headers using most of the signature headers elements. This is joining all of the 'Key=Value' elements again
+ ## and only URL encoding the Values this time while including non-URL encoded double quotes around each value
+ $AuthorizationParams = $SignatureParams
+ $AuthorizationParams.Add('oauth_signature', $OauthSignature)
+
+ ## Remove any API call-specific params from the authorization params
+ $AuthorizationParams.Remove('status')
+ $AuthorizationParams.Remove('text')
+ $AuthorizationParams.Remove('screen_name')
+
+ $AuthorizationString = 'OAuth '
+ $AuthorizationParams.GetEnumerator() | sort name | foreach { $AuthorizationString += $_.Key + '="' + [System.Uri]::EscapeDataString($_.Value) + '", ' }
+ $AuthorizationString = $AuthorizationString.TrimEnd(', ')
+ Write-Verbose "Using authorization string '$AuthorizationString'"
+
+ $AuthorizationString
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
+
+function Send-Tweet {
+ <#
+ .SYNOPSIS
+ This sends a tweet under a username.
+ .EXAMPLE
+ Send-Tweet -Message 'hello, world'
+
+ This example will send a tweet with the text 'hello, world'.
+ .PARAMETER Message
+ The text of the tweet.
+ #>
+ [CmdletBinding()]
+ [OutputType('System.Management.Automation.PSCustomObject')]
+ param (
+ [Parameter(Mandatory)]
+ [ValidateLength(1, 140)]
+ [string]$Message
+ )
+
+ process {
+ $HttpEndPoint = 'https://api.twitter.com/1.1/statuses/update.json'
+
+ $AuthorizationString = Get-OAuthAuthorization -TweetMessage $Message -HttpEndPoint $HttpEndPoint
+
+ ## Convert the message to a Byte array
+ $Body = [System.Text.Encoding]::ASCII.GetBytes("status=$Message");
+ Write-Verbose "Using POST body '$Body'"
+ Invoke-RestMethod -URI $HttpEndPoint -Method Post -Body $Body -Headers @{ 'Authorization' = $AuthorizationString } -ContentType "application/x-www-form-urlencoded"
+ }
+}
+
+function Send-TwitterDm {
+ <#
+ .SYNOPSIS
+ This sends a DM to another Twitter user.
+ .EXAMPLE
+ Send-TwitterDm -Message 'hello, Adam' -Username 'adam'
+
+ This sends a DM with the text 'hello, Adam' to the username 'adam'
+ .PARAMETER Message
+ The text of the DM.
+ .PARAMETER Username
+ The username you'd like to send the DM to.
+ #>
+ [CmdletBinding(DefaultParameterSetName='None')]
+ [OutputType('System.Management.Automation.PSCustomObject')]
+ param (
+ [Parameter(Mandatory)]
+ [ValidateLength(1, 140)]
+ [string]$Message,
+ [Parameter(Mandatory,ParameterSetName='Username')]
+ [string]$Username
+ )
+
+ process {
+ $HttpEndPoint = 'https://api.twitter.com/1.1/direct_messages/new.json'
+
+ $AuthorizationString = Get-OAuthAuthorization -DmMessage $Message -HttpEndPoint $HttpEndPoint -Username $Username -Verbose
+
+ ## Convert the message to a Byte array
+ $Message = [System.Uri]::EscapeDataString($Message)
+ $Username = [System.Uri]::EscapeDataString($Username)
+ $Body = [System.Text.Encoding]::ASCII.GetBytes("text=$Message&screen_name=$Username");
+ Write-Verbose "Using POST body '$Body'"
+ Invoke-RestMethod -URI $HttpEndPoint -Method Post -Body $Body -Headers @{ 'Authorization' = $AuthorizationString } -ContentType "application/x-www-form-urlencoded"
+
+ }
+}
\ No newline at end of file
diff --git a/Send-WolProxyRequest.ps1 b/Send-WolProxyRequest.ps1
new file mode 100644
index 0000000..36ac85d
--- /dev/null
+++ b/Send-WolProxyRequest.ps1
@@ -0,0 +1,462 @@
+#Requires -Version 3
+
+<#
+.NOTES
+ Created on: 5/21/2014 1:33 PM
+ Created by: Adam Bertram
+ Filename: Send-WolProxyRequest.ps1
+ General Requirements: Read access to a System Center Configuration Manager database
+ Requirements:
+ Wake On LAN Command Line Utility (http://www.depicus.com/wake-on-lan/wake-on-lan-cmd.aspx)
+ Todos:
+ Remove the dependency on wolcmd.exe. Use the Net.Sockets.UdpClient object instead.
+ Speed this up by using jobs
+.DESCRIPTION
+ This script is designed to send a WOL magic packet to a specified computer. If the specified computer is not
+ on the same network as the originatnating computer, it will attempt to find a "proxy" Windows computer to
+ initiate the WOL send. This gets around traditional network multicasting requirements.
+
+ This script currently requires access to a System Center Configuration Manager database. This
+ script uses it to find the various network information about the specified computer to wake.
+
+ This script uses a text file to store known good candidates to be used as WOL proxies. It can either
+ be prepopulated with Windows PCs or as you use this script more the script will populate it with
+ all of the WOL proxies it picks as to speed up the selection process.
+.EXAMPLE
+ .\Send-WolProxyRequest.ps1 -Computername COMPUTERNAME
+.EXAMPLE
+ .\Send-WolProxyRequest.ps1 -Computername COMPUTERNAME -UsePsRemoting
+.PARAMETER Computername
+ This computer name that you'd like to attempt to wake up.
+.PARAMETER ConfigMgrSite
+ The site code of your ConfigMgr site
+.PARAMETER ConfigMgrSiteServer
+ The computer name of your ConfigMgr site server hosting your database
+.PARAMETER WolCmdFilePath
+ The file path where the wolcmd.exe utility is located
+.PARAMETER UsePsRemoting
+ Use this switch if you'd like to use Powershell remoting to kick off the WOL attempt on the WOL proxy
+ rather than using WMI to initiate the remote process
+.PARAMETER KnownGoodWolProxyHostsFilePath
+ The file path to the text file that contains any Windows computers that were previously
+ found to be suitable WOL proxies.
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $True,
+ ValueFromPipeline = $True,
+ ValueFromPipelineByPropertyName = $True)]
+ [string]$Computername,
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [string]$ConfigMgrSite = 'UHP',
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [ValidateScript({ Test-Connection $_ -Quiet -Count 1 })]
+ [string]$ConfigMgrSiteServer = 'CONFIGMANAGER',
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [ValidateScript({ Test-Path $_ })]
+ [string]$WolCmdFilePath = '\\hosp.uhhg.org\netlogon\wolcmd.exe',
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [switch]$UsePsRemoting = $false,
+ [Parameter(Mandatory = $False,
+ ValueFromPipeline = $False,
+ ValueFromPipelineByPropertyName = $False)]
+ [string]$KnownGoodWolProxyHostsFilePath = "$($env:USERPROFILE)\desktop\KnownGoodWolProxies.txt"
+)
+
+begin {
+
+ function ConvertTo-DecimalIP {
+ <#
+ .Synopsis
+ Converts a Decimal IP address into a 32-bit unsigned integer.
+ .Description
+ ConvertTo-DecimalIP takes a decimal IP, uses a shift-like operation on each octet and returns a single UInt32 value.
+ http://www.indented.co.uk/2010/01/23/powershell-subnet-math/
+ .Parameter IPAddress
+ An IP Address to convert.
+ #>
+
+ [CmdLetBinding()]
+ param (
+ [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
+ [Net.IPAddress]$IPAddress
+ )
+
+ process {
+ $i = 3; $DecimalIP = 0;
+ $IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }
+
+ return [UInt32]$DecimalIP
+ }
+ }
+
+ function ConvertTo-DottedDecimalIP {
+ <#
+ .Synopsis
+ Returns a dotted decimal IP address from either an unsigned 32-bit integer or a dotted binary string.
+ .Description
+ ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address.
+ http://www.indented.co.uk/2010/01/23/powershell-subnet-math/
+ .Parameter IPAddress
+ A string representation of an IP address from either UInt32 or dotted binary.
+ #>
+
+ [CmdLetBinding()]
+ param (
+ [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
+ [String]$IPAddress
+ )
+
+ process {
+ Switch -RegEx ($IPAddress) {
+ "([01]{8}.){3}[01]{8}" {
+ return [String]::Join('.', $($IPAddress.Split('.') | ForEach-Object { [Convert]::ToUInt32($_, 2) }))
+ }
+ "\d" {
+ $IPAddress = [UInt32]$IPAddress
+ $DottedIP = $(For ($i = 3; $i -gt -1; $i--) {
+ $Remainder = $IPAddress % [Math]::Pow(256, $i)
+ ($IPAddress - $Remainder) / [Math]::Pow(256, $i)
+ $IPAddress = $Remainder
+ })
+
+ return [String]::Join('.', $DottedIP)
+ }
+ default {
+ Write-Error "Cannot convert this format"
+ }
+ }
+ }
+ }
+
+ function Get-NetworkAddress {
+ <#
+ .Synopsis
+ Takes an IP address and subnet mask then calculates the network address for the range.
+ .Description
+ Get-NetworkAddress returns the network address for a subnet by performing a bitwise AND
+ operation against the decimal forms of the IP address and subnet mask. Get-NetworkAddress
+ expects both the IP address and subnet mask in dotted decimal format.
+ http://www.indented.co.uk/2010/01/23/powershell-subnet-math/
+ .Parameter IPAddress
+ Any IP address within the network range.
+ .Parameter SubnetMask
+ The subnet mask for the network.
+ #>
+
+ [CmdLetBinding()]
+ param (
+ [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
+ [Net.IPAddress]$IPAddress,
+
+ [Parameter(Mandatory = $true, Position = 1)]
+ [Alias("Mask")]
+ [Net.IPAddress]$SubnetMask
+ )
+
+ process {
+ [pscustomobject]@{ 'NetworkAddress' = (ConvertTo-DottedDecimalIP ((ConvertTo-DecimalIP $IPAddress) -band (ConvertTo-DecimalIP $SubnetMask))) }
+ }
+ }
+
+ function ConvertTo-Mask {
+ <#
+ .Synopsis
+ Returns a dotted decimal subnet mask from a mask length.
+ .Description
+ ConvertTo-Mask returns a subnet mask in dotted decimal format from an integer value ranging
+ between 0 and 32. ConvertTo-Mask first creates a binary string from the length, converts
+ that to an unsigned 32-bit integer then calls ConvertTo-DottedDecimalIP to complete the operation.
+ .Parameter MaskLength
+ The number of bits which must be masked.
+ #>
+
+ [CmdLetBinding()]
+ param (
+ [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
+ [Alias("Length")]
+ [ValidateRange(0, 32)]
+ $MaskLength
+ )
+
+ Process {
+ return ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($(("1" * $MaskLength).PadRight(32, "0")), 2))
+ }
+ }
+
+ function Get-NetworkRange([String]$IP, [String]$Mask) {
+ if ($IP.Contains("/")) {
+ $Temp = $IP.Split("/")
+ $IP = $Temp[0]
+ $Mask = $Temp[1]
+ }
+
+ if (!$Mask.Contains(".")) {
+ $Mask = ConvertTo-Mask $Mask
+ }
+
+ $DecimalIP = ConvertTo-DecimalIP $IP
+ $DecimalMask = ConvertTo-DecimalIP $Mask
+
+ $Network = $DecimalIP -band $DecimalMask
+ $Broadcast = $DecimalIP -bor ((-bnot $DecimalMask) -band [UInt32]::MaxValue)
+
+ for ($i = $($Network + 1); $i -lt $Broadcast; $i++) {
+ ConvertTo-DottedDecimalIP $i
+ }
+ }
+
+ function Get-OfflineComputerNetworkInformation ($Computername) {
+ $WmiQuery = "SELECT DISTINCT *
+ FROM SMS_R_System AS sys
+ JOIN SMS_G_System_NETWORK_ADAPTER_CONFIGURATION AS net ON net.ResourceID = sys.ResourceID
+ WHERE sys.Name = '$ComputerName' AND
+ net.IPAddress IS NOT NULL"
+
+ $WmiParams = @{
+ 'ComputerName' = $ConfigMgrSiteServer
+ 'Namespace' = "root\sms\site_$ConfigMgrSite"
+ 'Query' = $WmiQuery
+ }
+
+ ## Query all network interfaces on the local machine and parse out IP address, subnet mask and the MAC
+ Write-Verbose "Querying site server '$ConfigMgrSiteServer' with query '$WmiQuery'"
+ try {
+ $Output = @{ }
+ $NetworkInfo = Get-WmiObject @WmiParams
+ if (!$NetworkInfo) {
+ throw "Computer '$Computername' could not be found in the SCCM database"
+ } else {
+ $NetworkInfo | foreach {
+ $Output.IPAddress = [string]([regex]'\b(?:\d{1,3}\.){3}\d{1,3}\b').Matches($_.net.IPAddress)
+ $Output.SubnetMask = [string]([regex]'\b(?:\d{1,3}\.){3}\d{1,3}\b').Matches($_.net.IPSubnet)
+ $Output.MACAddress = [string](($_.net.MACAddress.replace(":", "")).replace("-", "")).replace(".", "")
+ }
+ }
+ [pscustomobject]$Output
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+
+ function Get-LocalIpNetwork {
+ Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = 'True'" | where { $_.IPAddress -and $_.IPSubnet } | foreach {
+ [pscustomobject]@{ 'LocalIPNetwork' = (Get-NetworkAddress -IPAddress $_.IPAddress[0] -SubnetMask $_.IPSubnet[0]) }
+ }
+ }
+
+ function Test-Ping {
+ param ($ComputerName)
+ try {
+ $oPing = new-object system.net.networkinformation.ping;
+ if (($oPing.Send($ComputerName, 200).Status -eq 'TimedOut')) {
+ $false
+ } else {
+ $true
+ }
+ } catch [System.Exception] {
+ $false
+ }##endtry
+ }
+
+ function Test-Wmi ($IpAddress) {
+ try {
+ $Result = ([WMICLASS]"\\$IpAddress\Root\CIMV2:Win32_Process").create("hostname")
+ if ($Result.ReturnValue -eq 0) {
+ $true
+ } else {
+ $false
+ }
+ } catch {
+ $false
+ }
+ }
+
+ function Validate-IsValidHost ($IpAddress) {
+ try {
+ Write-Verbose "Testing $IpAddress Ping"
+ if (Test-Ping -ComputerName $IpAddress) {
+ Write-Verbose "Testing $IpAddress Ping - Success"
+ ## Assume if the C$ share is available, it's a Windows computer we can
+ ## copy the wolcmd utility to and run. This could be better.
+ Write-Verbose "Testing $IpAddress SMB share file copy and removal"
+ ## Create a temp text file and try to copy it over to test access
+ $TestFilePath = "$($env:SystemDrive)\testcopy.txt"
+ Add-Content -Path $TestFilePath -Value '' -Force
+ Copy-Item -Path $TestFilePath -Destination "\\$IpAddress\c$" -Force
+ Remove-Item -Path "\\$IpAddress\c$\testcopy.txt" -Force
+ ## If it hasn't thrown an error yet then we've confirmed we can copy and delete a file from it
+ Write-Verbose "Testing $IpAddress SMB share file copy and removal - Success"
+ Write-Verbose "Testing $IpAddress remote WMI process creation"
+ if (Test-Wmi -IpAddress $IpAddress) {
+ Write-Verbose "Testing $IpAddress remote WMI process creation - Success"
+ Write-Verbose "All tests passed. $IpAddress is a good WOL proxy"
+ $true
+ } else {
+ throw 'Remote process could not be created with WMI'
+ }
+ } else {
+ throw 'Host is offline'
+ }
+ } catch {
+ Write-Warning "Test failed with error '$($_.Exception.Message)' for $IpAddress"
+ $false
+ }
+ }
+
+ function Get-WolProxy ($IpAddress, $SubnetMask) {
+ ## Check if any known good WOL proxy exists before scanning all the IPs
+ ## in that network
+ $IpNetwork = Get-NetworkAddress -IPAddress $IpAddress -SubnetMask $SubnetMask
+ $KnownGoodWolProxy = Get-KnownGoodWolProxy -IpNetwork $IpNetwork.NetworkAddress
+ if ($KnownGoodWolProxy) {
+ return $KnownGoodWolProxy
+ } else {
+ $HostIps = Get-NetworkRange -IP $IpAddress -Mask $SubnetMask
+ foreach ($Ip in $HostIps) {
+ Write-Verbose "Checking $Ip if good candidate for WOL proxy..."
+ ## Check to see if our WOL proxy PC is online
+ if (Validate-IsValidHost -IpAddress $Ip) {
+ Write-Verbose "WOL Proxy found: $Ip"
+
+ ## Add this computer to the known good proxy list for later
+ New-KnownGoodWolProxy -HostProxy @{ 'IpAddress' = $Ip; 'SubnetMask' = $SubnetMask }
+
+ return $Ip
+ } else {
+ Write-Verbose "IP address '$Ip' will not work as a WOL proxy"
+ }
+ }
+ $false
+ }
+ }
+
+ ## WOL proxy Windows hosts that are online and accessible
+ function New-KnownGoodWolProxy ([hashtable]$HostProxy) {
+ if (!(Get-KnownGoodWolProxy $HostProxy.IpAddress)) {
+ Write-Verbose "The '$($HostProxy.IpAddress)' host is not yet in the known good WOL proxy host file."
+ ## Create IP network from IP address and subnet mask
+ $IpNetwork = Get-NetworkAddress -IPAddress $HostProxy.IpAddress -SubnetMask $HostProxy.SubnetMask
+ Write-Verbose "Adding IP Address: $($HostProxy.IpAddress) IP Network: $IpNetwork SubnetMask: $($HostProxy.SubnetMask) to known good WOL proxy host file"
+ [pscustomobject]@{
+ 'IpAddress' = $HostProxy.IpAddress;
+ 'IpNetwork' = $IpNetwork.NetworkAddress;
+ 'SubnetMask' = $HostProxy.SubnetMask
+ } | Export-Csv -Path $KnownGoodWolProxyHostsFilePath -Append -NoTypeInformation
+ }
+
+ }
+
+ function Remove-KnownGoodWolProxy ($IpAddress) {
+ $NewCsvContents = Import-Csv -Path $KnownGoodWolProxyHostsFilePath | where { $_.IpAddress -ne $IpAddress }
+ ##TODO: This removes the row and headers if only 1 row exists
+ $NewCsvContents | Export-Csv -Path $KnownGoodWolProxyHostsFilePath -NoTypeInformation
+ }
+
+ ## Searches the known good WOL proxy file for an accessible host in the IP network specified
+ function Get-KnownGoodWolProxy ([string]$IpNetwork) {
+ if (!(Test-Path $KnownGoodWolProxyHostsFilePath)) {
+ Write-Verbose "Known good WOL proxy host file at '$KnownGoodWolProxyHostsFilePath' does not exist"
+ $false
+ } else {
+ $Hosts = Import-Csv -Path $KnownGoodWolProxyHostsFilePath | where { $_.IpNetwork -eq $IpNetwork }
+ if (!$Hosts) {
+ Write-Verbose "No known good WOL proxy hosts found in IP network '$IpNetwork'"
+ $false
+ } else {
+ $HostsOnNet = $Hosts | where { $_.IpNetwork -eq $IpNetwork }
+ if ($HostsOnNet) {
+ Write-Verbose "$(($HostsOnNet | measure -Sum -ea silentlycontinue).Count) (unknown accessibility) known good WOL proxy hosts found on IP network '$IpNetwork'"
+ Write-Verbose 'Checking previously known good WOL proxy hosts if still usable'
+ $AccessibleHost = $HostsOnNet | where { Validate-IsValidHost $_.IpAddress } | select -First 1 -ExpandProperty IpAddress
+ if ($AccessibleHost) {
+ Write-Verbose "Found hostname '$AccessibleHost' still to be a good WOL proxy host"
+ $AccessibleHost
+ } else {
+ Remove-KnownGoodWolProxy -IpAddress $_.IpAddress
+ }
+ } else {
+ Write-Verbose "No accessible, known good WOL proxy hosts on the '$IpNetwork' found"
+ }
+ }
+ }
+ }
+
+ function Send-WolPacketLocally ($MacAddress, $IpNetwork, $SubnetMask) {
+ & $WolCmdFilePath $MacAddress $IPNetwork $SubnetMask $WolUdpPort 2>&1> $null
+ }
+
+ function Invoke-WolProxy ($IpAddress, $OfflineMacAddress, $OfflineIpNetwork, $OfflineSubnetMask) {
+ ## Remove the dependency on wolcmd.exe. Use the Net.Sockets.UdpClient object instead.
+ ## Copy wolcmd to the remote proxy computer
+ Write-Verbose "Copying $WolCmdFilePath to \\$IpAddress\c`$..."
+ Copy-Item $WolCmdFilePath "\\$IpAddress\c$" -Force
+
+ $WolCmdString = "C:\$($WolCmdFilePath | Split-Path -Leaf) $OfflineMacAddress $OfflineIPNetwork $OfflineSubnetMask $WolUdpPort"
+ Write-Verbose "Initiating the string `"$WolCmdString`"..."
+ Write-Verbose "Connecting to $IpAddress and attempting WOL proxy function via WMI RPC method..."
+ $Result = ([WMICLASS]"\\$IpAddress\Root\CIMV2:Win32_Process").create($WolCmdString)
+ if ($Result) {
+ Write-Verbose "Waiting for process ID $($Result.ProcessID) on IP $IpAddress..."
+ while (Get-Process -Id $Result.ProcessID -ComputerName $IpAddress -ErrorAction 'SilentlyContinue') {
+ sleep 1
+ }
+ Write-Verbose "Process ID $($Result.ProcessID) has exited"
+ } else {
+ Write-Warning "Failed to initiate WMI process creation on '$IpAddress'. Exit code was '$($NewProcess.ReturnValue)'"
+ }
+ #}
+ ## Cleanup all files copied to the proxy computer
+ Write-Verbose 'Cleaning up file remnants on WOL proxy computer...'
+ if (Test-Path "\\$IpAddress\c`$\$($WolCmdFilePath | Split-Path -Leaf)") {
+ Remove-Item -Path "\\$IpAddress\c`$\$($WolCmdFilePath | Split-Path -Leaf)" -Force
+ }
+ }
+
+ if (Test-Connection -ComputerName $Computername -Quiet -Count 1) {
+ Write-Verbose -Message "The computer $Computername is already online"
+ return
+ }
+
+ ## Find all of the IP/subnet masks on the local computer
+ $LocalIPAddressNetworks = Get-LocalIpNetwork
+ Write-Verbose "Found $($LocalIPAddressNetworks.Count) local IP networks"
+
+ ## Common WOL UDP ports are 7 and 9
+ $WolUdpPort = 9
+
+ $OfflineComputerNetwork = Get-OfflineComputerNetworkInformation $Computername
+
+}
+process {
+ try {
+ ## TODO: Make this run in parallel by jobs of a foreach -parallel workflow loop
+ foreach ($Network in $OfflineComputerNetwork) {
+ Write-Verbose "Processing IP address $($Network.IPAddress)..."
+ Write-Verbose "Checking the remote network to see if it's on any local IP network..."
+ $RemoteIpNetwork = Get-NetworkAddress -IPAddress $Network.IpAddress -SubnetMask $Network.SubnetMask
+ if ($LocalIPNetworks.LocalIPNetwork -contains $RemoteIpNetwork.NetworkAddress) {
+ Write-Verbose 'IP found to be on local subnet. No WOL proxy needed. Sending WOL directly to the intended machine...'
+ Send-WolPacketLocally -MacAddress $Network.MacAddress -IpNetwork $RemoteIpNetwork.NetworkAddress -SubnetMask $Network.SubnetMask
+ } else {
+ Write-Verbose 'IP not found to be on local subnet. Getting WOL proxy computer...'
+ $WolProxy = Get-WolProxy -IpAddress $Network.IPAddress -SubnetMask $Network.SubnetMask
+ if (!$WolProxy) {
+ Write-Warning "Unable to find a WOL proxy for '$Computername'"
+ } else {
+ Invoke-WolProxy -OfflineIpNetwork $RemoteIpNetwork.NetworkAddress -OfflineMacAddress $Network.MacAddress -OfflineSubnetMask $Network.SubnetMask -IpAddress $WolProxy
+ }
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Set-Exchange2013AdSchema.ps1 b/Set-Exchange2013AdSchema.ps1
new file mode 100644
index 0000000..7790177
--- /dev/null
+++ b/Set-Exchange2013AdSchema.ps1
@@ -0,0 +1,119 @@
+#Requires -Module ActiveDirectory
+
+<#
+.SYNOPSIS
+ This script is designed to expedite the AD schema extension for Exchange 2013. It is an automated way to not only
+ perform the schema change itself but also to confirm success as well. This script is intended to be run on a domain-joined
+ workstation under an account that is both in the Schema Admins group and the Enterprise Admins group.
+
+ It will require the Exhcange 2013 media (eval or licensed)
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename: Set-Exchange2013AdSchema.ps1
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER ExchangeMediaFolderPath
+ The path to where the contents of the Exchange 2013 media is located. This is typically the ISO extracted.
+
+#>
+[CmdletBinding()]
+[OutputType([bool])]
+param (
+ [Parameter(Mandatory)]
+ [ValidateScript({Test-Path $_ -PathType 'Container'})]
+ [string]$ExchangeMediaFolderPath
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+
+ function Test-GroupMembership {
+ $RequiredGroups = 'Schema Admins', 'Enterprise Admins'
+ $Username = whoami
+ $Username = $Username.Split('\')[1]
+ $Groups = (Get-ADUser -Identity $Username -Properties Memberof).MemberOf | foreach { $_.Split(',')[0].TrimStart('CN=') }
+ if (($Groups | where { $RequiredGroups -contains $_ }).Count -ne 2) {
+ $false
+ } else {
+ $true
+ }
+ }
+
+ function Get-InstalledSoftwareInRegistry {
+ <#
+ .SYNOPSIS
+ Retrieves a list of all software installed
+ .DESCRIPTION
+ Retrieves a list of all software installed via the specified method
+ .EXAMPLE
+ Get-InstalledSoftware
+ This example retrieves all software installed on the local computer
+ .PARAMETER Computername
+ Use this parameter if you'd like to query installed software on a remote computer
+ #>
+ [CmdletBinding()]
+ param (
+ [string]$Computername = 'localhost'
+ )
+ process {
+ try {
+ $ScriptBlock = {
+ $UninstallKeys = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
+ New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null
+ $UninstallKeys += Get-ChildItem HKU: | where { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | foreach { "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall" }
+ foreach ($UninstallKey in $UninstallKeys) {
+ $Keys = Get-ItemProperty -Path "$UninstallKey\*" -ErrorAction SilentlyContinue
+ foreach ($Key in ($Keys | where { $_.SystemComponent -ne '1' })) {
+ $Key
+ }
+ }
+ }
+ if ($Computername -ne 'localhost') {
+ Invoke-Command -ComputerName $Computername -ScriptBlock $ScriptBlock
+ } else {
+ & $ScriptBlock
+ }
+ } catch {
+ $_.Exception.Message
+ }
+ }
+ }
+}
+
+process {
+ try {
+ ## Ensure the account this is being run under is in the appropriate groups
+ if (-not (Test-GroupMembership)) {
+ throw "The user is not in the proper groups"
+ }
+
+ ## Ensure .NET 4.5 and at least PSv3 is installed on the schema master
+ $SchemaMaster = (Get-ADForest).SchemaMaster
+
+ ## Disable replication on the schema master to prevent any potential problems from replicating out
+
+ ## Perform the schema extension
+
+ ## Verify the extension was successful by checking the log file
+
+ ## Verify the extension was successful by checking for the msExch attribute on the user
+
+ ## Reenable replication
+
+ ## Ensure replication is successful
+ } catch {
+ Write-Error "$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+}
+
+end {
+ try {
+
+ } catch {
+ Write-Error "$($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ }
+}
\ No newline at end of file
diff --git a/Set-MsRandomPasswordToLocalUserAccount.ps1 b/Set-MsRandomPasswordToLocalUserAccount.ps1
new file mode 100644
index 0000000..b050fbe
--- /dev/null
+++ b/Set-MsRandomPasswordToLocalUserAccount.ps1
@@ -0,0 +1,267 @@
+function Invoke-PasswordRoll {
+ <#
+ .SYNOPSIS
+
+ This script can be used to set the local account passwords on remote machines to random passwords. The username/password/server combination will be saved in a CSV file.
+ The account passwords stored in the CSV file can be encrypted using a password of the administrators choosing to ensure clear-text account passwords aren't written to disk.
+ The encrypted passwords can be decrypted using another function in this file: ConvertTo-CleartextPassword
+
+
+ Function: Invoke-PasswordRoll
+ Author: Microsoft
+ Version: 1.0
+
+ .DESCRIPTION
+
+ This script can be used to set the local account passwords on remote machines to random passwords. The username/password/server combination will be saved in a CSV file.
+ The account passwords stored in the CSV file can be encrypted using a password of the administrators choosing to ensure clear-text account passwords aren't written to disk.
+ The encrypted passwords can be decrypted using another function in this file: ConvertTo-CleartextPassword
+
+ .PARAMETER ComputerName
+
+ An array of computers to run the script against using PowerShell remoting.
+
+ .PARAMETER LocalAccounts
+
+ An array of local accounts whose password should be changed.
+
+ .PARAMETER TsvFileName
+
+ The file to output the username/password/server combinations to.
+
+ .PARAMETER EncryptionKey
+
+ A password to encrypt the TSV file with. Uses AES encryption. Only the passwords stored in the TSV file will be encrypted, the username and servername will be clear-text.
+
+ .PARAMETER PasswordLength
+
+ The length of the passwords which will be randomly generated for local accounts.
+
+ .PARAMETER NoEncryption
+
+ Do not encrypt the account passwords stored in the TSV file. This will result in clear-text passwords being written to disk.
+
+ .EXAMPLE
+
+ . .\Invoke-PasswordRoll.ps1 #Loads the functions in this script file
+ Invoke-PasswordRoll -ComputerName (Get-Content computerlist.txt) -LocalAccounts @("administrator","CustomLocalAdmin") -TsvFileName "LocalAdminCredentials.tsv" -EncryptionKey "Password1"
+
+ Connects to all the computers stored in the file "computerlist.txt". If the local account "administrator" and/or "CustomLocalAdmin" are present on the system, their password is changed
+ to a randomly generated password of length 20 (the default). The username/password/server combinations are stored in LocalAdminCredentials.tsv, and the account passwords are AES encrypted using the password "Password1".
+
+ .EXAMPLE
+
+ . .\Invoke-PasswordRoll.ps1 #Loads the functions in this script file
+ Invoke-PasswordRoll -ComputerName (Get-Content computerlist.txt) -LocalAccounts @("administrator") -TsvFileName "LocalAdminCredentials.tsv" -NoEncryption -PasswordLength 40
+
+ Connects to all the computers stored in the file "computerlist.txt". If the local account "administrator" is present on the system, its password is changed to a random generated
+ password of length 40. The username/password/server combinations are stored in LocalAdminCredentials.tsv unencrypted.
+
+ .NOTES
+ Requirements:
+ -PowerShellv2 or above must be installed
+ -PowerShell remoting must be enabled on all systems the script will be run against
+
+ Script behavior:
+ -If a local account is present on the system, but not specified in the LocalAccounts parameter, the script will write a warning to the screen to alert you to the presence of this local account. The script will continue running when this happens.
+ -If a local account is specified in the LocalAccounts parameter, but the account does not exist on the computer, nothing will happen (an account will NOT be created).
+ -The function ConvertTo-CleartextPassword, contained in this file, can be used to decrypt passwords that are stored encrypted in the TSV file.
+ -If a server specified in ComputerName cannot be connected to, PowerShell will output an error message.
+ -Microsoft advises companies to regularly roll all local and domain account passwords.
+
+ #>
+ [CmdletBinding(DefaultParameterSetName = "Encryption")]
+ Param (
+ [Parameter(Mandatory = $true)]
+ [String[]]
+ $ComputerName,
+
+ [Parameter(Mandatory = $true)]
+ [String[]]
+ $LocalAccounts,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $TsvFileName,
+
+ [Parameter(ParameterSetName = "Encryption", Mandatory = $true)]
+ [String]
+ $EncryptionKey,
+
+ [Parameter()]
+ [ValidateRange(20, 120)]
+ [Int]
+ $PasswordLength = 20,
+
+ [Parameter(ParameterSetName = "NoEncryption", Mandatory = $true)]
+ [Switch]
+ $NoEncryption
+ )
+
+
+ #Load any needed .net classes
+ Add-Type -AssemblyName "System.Web" -ErrorAction Stop
+
+
+ #This is the scriptblock that will be executed on every computer specified in ComputerName
+ $RemoteRollScript = {
+ Param (
+ [Parameter(Mandatory = $true, Position = 1)]
+ [String[]]
+ $Passwords,
+
+ [Parameter(Mandatory = $true, Position = 2)]
+ [String[]]
+ $LocalAccounts,
+
+ #This is here so I can record what the server name that the script connected to was, sometimes the DNS records get messed up, it can be nice to have this.
+ [Parameter(Mandatory = $true, Position = 3)]
+ [String]
+ $TargettedServerName
+ )
+
+ $LocalUsers = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=true" | Foreach { $_.Name }
+
+ #Check if the computer has any local user accounts whose passwords are not going to be rolled by this script
+ foreach ($User in $LocalUsers) {
+ if ($LocalAccounts -inotcontains $User) {
+ Write-Warning "Server: '$($TargettedServerName)' has a local account '$($User)' whos password is NOT being changed by this script"
+ }
+ }
+
+ #For every local account specified that exists on this server, change the password
+ $PasswordIndex = 0
+ foreach ($LocalAdmin in $LocalAccounts) {
+ $Password = $Passwords[$PasswordIndex]
+
+ if ($LocalUsers -icontains $LocalAdmin) {
+ try {
+ $objUser = [ADSI]"WinNT://localhost/$($LocalAdmin), user"
+ $objUser.psbase.Invoke("SetPassword", $Password)
+
+ $Properties = @{
+ TargettedServerName = $TargettedServerName
+ Username = $LocalAdmin
+ Password = $Password
+ RealServerName = $env:computername
+ }
+
+ $ReturnData = New-Object PSObject -Property $Properties
+ Write-Output $ReturnData
+ } catch {
+ Write-Error "Error changing password for user:$($LocalAdmin) on server:$($TargettedServerName)"
+ }
+ }
+
+ $PasswordIndex++
+ }
+ }
+
+
+ #Generate the password on the client running this script, not on the remote machine. System.Web.Security isn't available in the .NET Client profile. Making this call
+ # on the client running the script ensures only 1 computer needs the full .NET runtime installed (as opposed to every system having the password rolled).
+ function Create-RandomPassword {
+ Param (
+ [Parameter(Mandatory = $true)]
+ [ValidateRange(20, 120)]
+ [Int]
+ $PasswordLength
+ )
+
+ $Password = [System.Web.Security.Membership]::GeneratePassword($PasswordLength, $PasswordLength / 4)
+
+ #This should never fail, but I'm putting a sanity check here anyways
+ if ($Password.Length -ne $PasswordLength) {
+ throw new Exception("Password returned by GeneratePassword is not the same length as required. Required length: $($PasswordLength). Generated length: $($Password.Length)")
+ }
+
+ return $Password
+ }
+
+
+ #Main functionality - Generate a password and remote in to machines to change the password of local accounts specified
+ if ($PsCmdlet.ParameterSetName -ieq "Encryption") {
+ try {
+ $Sha256 = new-object System.Security.Cryptography.SHA256CryptoServiceProvider
+ $SecureStringKey = $Sha256.ComputeHash([System.Text.UnicodeEncoding]::Unicode.GetBytes($EncryptionKey))
+ } catch {
+ Write-Error "Error creating TSV encryption key" -ErrorAction Stop
+ }
+ }
+
+ foreach ($Computer in $ComputerName) {
+ #Need to generate 1 password for each account that could be changed
+ $Passwords = @()
+ for ($i = 0; $i -lt $LocalAccounts.Length; $i++) {
+ $Passwords += Create-RandomPassword -PasswordLength $PasswordLength
+ }
+
+ Write-Output "Connecting to server '$($Computer)' to roll specified local admin passwords"
+ $Result = Invoke-Command -ScriptBlock $RemoteRollScript -ArgumentList @($Passwords, $LocalAccounts, $Computer) -ComputerName $Computer
+ #If encryption is being used, encrypt the password with the user supplied key prior to writing to disk
+ if ($Result -ne $null) {
+ if ($PsCmdlet.ParameterSetName -ieq "NoEncryption") {
+ $Result | Select-Object Username, Password, TargettedServerName, RealServerName | Export-Csv -Append -Path $TsvFileName -NoTypeInformation
+ } else {
+ #Filters out $null entries returned
+ $Result = $Result | Select-Object Username, Password, TargettedServerName, RealServerName
+
+ foreach ($Record in $Result) {
+ $PasswordSecureString = ConvertTo-SecureString -AsPlainText -Force -String ($Record.Password)
+ $Record | Add-Member -MemberType NoteProperty -Name EncryptedPassword -Value (ConvertFrom-SecureString -Key $SecureStringKey -SecureString $PasswordSecureString)
+ $Record.PSObject.Properties.Remove("Password")
+ $Record | Select-Object Username, EncryptedPassword, TargettedServerName, RealServerName | Export-Csv -Append -Path $TsvFileName -NoTypeInformation
+ }
+ }
+ }
+ }
+}
+
+function ConvertTo-CleartextPassword {
+ <#
+ .SYNOPSIS
+ This function can be used to decrypt passwords that were stored encrypted by the function Invoke-PasswordRoll.
+
+ Function: ConvertTo-CleartextPassword
+ Author: Microsoft
+ Version: 1.0
+
+ .DESCRIPTION
+ This function can be used to decrypt passwords that were stored encrypted by the function Invoke-PasswordRoll.
+
+
+ .PARAMETER EncryptedPassword
+
+ The encrypted password that was stored in a TSV file.
+
+ .PARAMETER EncryptionKey
+
+ The password used to do the encryption.
+
+
+ .EXAMPLE
+
+ . .\Invoke-PasswordRoll.ps1 #Loads the functions in this script file
+ ConvertTo-CleartextPassword -EncryptionKey "Password1" -EncryptedPassword 76492d1116743f0423413b16050a5345MgB8AGcAZgBaAHUAaQBwADAAQgB2AGgAcABNADMASwBaAFoAQQBzADEAeABjAEEAPQA9AHwAZgBiAGYAMAA1ADYANgA2ADEANwBkADQAZgAwADMANABjAGUAZQAxAGIAMABiADkANgBiADkAMAA4ADcANwBhADMAYQA3AGYAOABkADcAMQA5ADQAMwBmAGYANQBhADEAYQBjADcANABkADIANgBhADUANwBlADgAMAAyADQANgA1ADIAOQA0AGMAZQA0ADEAMwAzADcANQAyADUANAAzADYAMAA1AGEANgAzADEAMQA5ADAAYwBmADQAZAA2AGQA"
+
+ Decrypts the encrypted password which was stored in the TSV file.
+
+ #>
+ Param (
+ [Parameter(Mandatory = $true)]
+ [String]
+ $EncryptedPassword,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $EncryptionKey
+ )
+
+ $Sha256 = new-object System.Security.Cryptography.SHA256CryptoServiceProvider
+ $SecureStringKey = $Sha256.ComputeHash([System.Text.UnicodeEncoding]::Unicode.GetBytes($EncryptionKey))
+
+ [SecureString]$SecureStringPassword = ConvertTo-SecureString -String $EncryptedPassword -Key $SecureStringKey
+ Write-Output ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SecureStringPassword)))
+}
+
+Invoke-PasswordRoll -ComputerName a-w7x86-1 -LocalAccounts 'aidet' -NoEncryption -TsvFileName 'file.tsv'
\ No newline at end of file
diff --git a/Set-RandomPasswordToLocalUserAccount.ps1 b/Set-RandomPasswordToLocalUserAccount.ps1
new file mode 100644
index 0000000..8ce9a42
--- /dev/null
+++ b/Set-RandomPasswordToLocalUserAccount.ps1
@@ -0,0 +1,195 @@
+#Requires -Version 3
+
+<#
+.SYNOPSIS
+ This finds a local or remote computer(s)' local administrator account and changes it to a random password.
+.DESCRIPTION
+ This finds a local or remote computer(s)' local administrator account, generates a random password,
+ changes the local administrator password to that random password then records the password into an output file.
+.NOTES
+ Created on: 7/19/2014
+ Created by: Adam Bertram
+ Filename: Set-RandomPasswordToLocalUserAccount.ps1
+ Credits: http://support.microsoft.com/kb/2962486
+ Todos: Add multiple user support
+.EXAMPLE
+ .\SetRandomPasswordToLocalUserAccount.ps1 -ComputerName 'COMPUTER1','COMPUTER2' -PasswordFilePath 'C:\passwords.txt'
+
+ This sets the local administrator account's password on COMPUTER1 and COMPUTER2 to a randomly generated password
+ and stores the history in a file called passwords.txt
+.EXAMPLE
+ .\SetRandomPasswordToLocalUserAccount.ps1 -ComputerName 'COMPUTER1','COMPUTER2' -PasswordFilePath 'C:\passwords.txt' -EncryptionKey 'key'
+
+ This sets the local administrator account's password on COMPUTER1 and COMPUTER2 to a randomly generated password
+ and stores encrypted passwords in a file called passwords.txt
+.EXAMPLE
+ .\SetRandomPasswordToLocalUserAccount.ps1 -ComputerName 'COMPUTER1','COMPUTER2' -PasswordFilePath 'C:\passwords.txt' -EncryptionKey 'key' -PasswordLength 100
+
+ This sets the local administrator account's password on COMPUTER1 and COMPUTER2 to a randomly generated 100 character password
+ and stores encrypted passwords in a file called passwords.txt
+.PARAMETER Computername
+ One or more computer names you'd like to change the local administrator password on. If no computer name is selected
+ then change the local administrator password on the local computer.
+.PARAMETER PasswordLength
+ The length of the password the new local adminsitrator password will be
+.PARAMETER PasswordFilePath
+ The file path to the output file where your passwords are stored
+.PARAMETER EncryptionKey
+ The encryption key you'd like applied to all password strings being being written to the password file
+.PARAMETER
+ If the administrator account is disabled, use this parameter to enable it. If this param is not used and the administrator
+ account is disabled, it will be skipped.
+#>
+[CmdletBinding()]
+ param (
+ [Parameter(ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateScript({ Test-Connection $_ -Quiet -Count 1 })]
+ [string[]]$Computername = 'localhost',
+ [Parameter()]
+ [ValidateRange(20, 120)]
+ [int]$PasswordLength = 50,
+ [Parameter(Mandatory)]
+ [string]$PasswordFilePath,
+ [Parameter()]
+ [string]$EncryptionKey,
+ [Parameter()]
+ [switch]$EnableAccount
+ )
+
+begin {
+ function Create-RandomPassword {
+ <#
+ .NOTES
+ Author: Microsoft
+ #>
+ Param (
+ [Parameter(Mandatory = $true)]
+ [ValidateRange(20, 120)]
+ [Int]$PasswordLength
+ )
+
+ $Password = [System.Web.Security.Membership]::GeneratePassword($PasswordLength, $PasswordLength / 4)
+
+ #This should never fail, but I'm putting a sanity check here anyways
+ if ($Password.Length -ne $PasswordLength) {
+ throw new Exception("Password returned by GeneratePassword is not the same length as required. Required length: $($PasswordLength). Generated length: $($Password.Length)")
+ }
+
+ return $Password
+ }
+
+ function Set-Encryption ($UnencryptedPassword, $EncryptionKey) {
+ try {
+ $PasswordSecureString = ConvertTo-SecureString -AsPlainText -Force -String $UnencryptedPassword
+
+ $Sha256 = new-object System.Security.Cryptography.SHA256CryptoServiceProvider
+ $SecureString = $Sha256.ComputeHash([System.Text.UnicodeEncoding]::Unicode.GetBytes($EncryptionKey))
+
+ ConvertFrom-SecureString -Key $SecureString -SecureString $PasswordSecureString
+ } catch {
+ Write-Error "Error creating encryption key" -ErrorAction Stop
+ $_.Exception.Message
+ }
+ }
+
+ function ConvertTo-CleartextPassword {
+ <#
+ .SYNOPSIS
+ This function can be used to decrypt passwords that were stored encrypted by the function Invoke-PasswordRoll.
+ .NOTES
+ Author: Microsoft
+ Version: 1.0
+ .DESCRIPTION
+ This function can be used to decrypt passwords that were stored encrypted by the function Invoke-PasswordRoll.
+ .PARAMETER EncryptedPassword
+ The encrypted password that was stored in a TSV file.
+ .PARAMETER EncryptionKey
+ The password used to do the encryption.
+ .EXAMPLE
+ . .\Invoke-PasswordRoll.ps1 #Loads the functions in this script file
+ ConvertTo-CleartextPassword -EncryptionKey "Password1" -EncryptedPassword 76492d1116743f0423413b16050a5345MgB8AGcAZgBaAHUAaQBwADAAQgB2AGgAcABNADMASwBaAFoAQQBzADEAeABjAEEAPQA9AHwAZgBiAGYAMAA1ADYANgA2ADEANwBkADQAZgAwADMANABjAGUAZQAxAGIAMABiADkANgBiADkAMAA4ADcANwBhADMAYQA3AGYAOABkADcAMQA5ADQAMwBmAGYANQBhADEAYQBjADcANABkADIANgBhADUANwBlADgAMAAyADQANgA1ADIAOQA0AGMAZQA0ADEAMwAzADcANQAyADUANAAzADYAMAA1AGEANgAzADEAMQA5ADAAYwBmADQAZAA2AGQA"
+
+ Decrypts the encrypted password which was stored in the TSV file.
+ #>
+ Param (
+ [Parameter(Mandatory)]
+ [String]$EncryptedPassword,
+
+ [Parameter(Mandatory)]
+ [String]$EncryptionKey
+ )
+
+ $Sha256 = new-object System.Security.Cryptography.SHA256CryptoServiceProvider
+ $SecureStringKey = $Sha256.ComputeHash([System.Text.UnicodeEncoding]::Unicode.GetBytes($EncryptionKey))
+
+ [SecureString]$SecureStringPassword = ConvertTo-SecureString -String $EncryptedPassword -Key $SecureStringKey
+ Write-Output ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SecureStringPassword)))
+ }
+
+ ## The System.Web is not a default assembly. This must be loaded in order to generate the random password
+ Add-Type -AssemblyName "System.Web" -ErrorAction Stop
+}
+
+process {
+ foreach ($Computer in $Computername) {
+ try {
+ $Properties = @{
+ ComputerName = $Computer
+ Username = ''
+ Password = ''
+ PasswordType = ''
+ Result = ''
+ EnabledAccount = ''
+ }
+ if (!(Test-Connection -ComputerName $Computer -Quiet -Count 1)) {
+ Write-Warning "Computer '$Computer' is not online"
+ $Properties.Result = 'Offline'
+ [pscustomobject]$Properties | Export-Csv -Path $PasswordFilePath -Delimiter "`t" -Append -NoTypeInformation
+ } else {
+ $LocalUsers = Get-WmiObject -ComputerName $Computer -Class Win32_UserAccount -Filter "LocalAccount=true"
+ Write-Verbose "Found $($LocalUsers.Count) local users on $Computer"
+ foreach ($LocalUser in $LocalUsers) {
+ Write-Verbose "--Checking username $($LocalUser.Name) for administrator account"
+ $oUser = [ADSI]"WinNT://$Computer/$($LocalUser.Name), user"
+ $Sid = $oUser.objectSid.ToString().Replace(' ', '')
+ if ($Sid.StartsWith('1500000521') -and $Sid.EndsWith('4100')) {
+ Write-Verbose "--Username $($LocalUser.Name)|SID '$Sid' is the local administrator account"
+ $LocalAdministrator = $LocalUser
+ break
+ }
+ }
+
+ $Properties.UserName = $LocalAdministrator.Name
+ Write-Verbose "Creating random password for $($LocalAdministrator.Name)"
+ $Password = Create-RandomPassword -PasswordLength $PasswordLength
+ if ($EncryptionKey) {
+ $Properties.PasswordType = 'Encrypted'
+ $Properties.Password = (Set-Encryption $Password $EncryptionKey)
+ } else {
+ $Properties.Password = $Password
+ $Properties.PasswordType = 'Unencrypted'
+ }
+
+ $oUser.psbase.Invoke("SetPassword", $Password)
+ $Properties.Result = 'Success'
+
+
+ Write-Verbose "Checking to ensure local administrator '$($LocalAdministrator.Name)' is enabled"
+ if ($LocalAdministrator.Disabled) {
+ Write-Verbose "Local administrator '$($LocalAdministrator.Name)' is disabled. Enabling..."
+ $Properties.EnabledAccount = 'True'
+ $LocalAdministrator.Disabled = $false
+ $LocalAdministrator.Put() | Out-Null
+ } else {
+ $Properties.EnabledAccount = 'False'
+ Write-Verbose "Local administrator '$($LocalAdministrator.Name)' is already enabled."
+ }
+
+ [pscustomobject]$Properties | Export-Csv -Path $PasswordFilePath -Delimiter "`t" -Append -NoTypeInformation
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+ }
+}
\ No newline at end of file
diff --git a/Set-ServiceAccount.ps1 b/Set-ServiceAccount.ps1
new file mode 100644
index 0000000..6b29216
--- /dev/null
+++ b/Set-ServiceAccount.ps1
@@ -0,0 +1,50 @@
+<#
+.SYNOPSIS
+ Changes the account a service runs under
+.NOTES
+ Created on: 11/28/2014
+ Created by: Adam Bertram
+ Filename: Set-ServiceAccount.ps1
+.EXAMPLE
+ PS> .\Set-ServiceAccount.ps1 -ServiceName 'snmp' -Computername 'COMPUTER1','COMPUTER2' -Username someuser -Password password12
+
+ This example changes the account the service snmp runs under to someuser on the computers COMPUTER1 and COMPUTER2
+.PARAMETER ServiceName
+ One or more service names
+.PARAMETER Computername
+ One or more remote computer names. This script defaults to the local computer.
+.PARAMETER Username
+ The username to change on the service
+.PARAMETER Password
+ The password of the username
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
+ [string[]]$ServiceName,
+ [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
+ [ValidateScript({Test-Connection -ComputerName $_ -Quiet -Count 1 })]
+ [string[]]$Computername = 'localhost',
+ [Parameter(Mandatory)]
+ [string]$Username,
+ [Parameter(Mandatory)]
+ [string]$Password
+)
+
+process {
+ foreach ($Computer in $Computername) {
+ foreach ($Service in $ServiceName) {
+ try {
+ Write-Verbose -Message "Changing service '$Service' on the computer '$Computer'"
+ $s = Get-WmiObject -ComputerName $Computer -Class Win32_Service -Filter "Name = '$Service'"
+ if (!$s) {
+ throw "The service '$Service' does not exist"
+ }
+ $s.Change($null, $null, $null, $null, $null, $null, $Username, $Password) | Out-Null
+ $s | Restart-Service -Force
+ } catch {
+ Write-Error -Message "Error: Computer: $Computer - Service: $Service - Error: $($_.Exception.Message)"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SetPrinterComments.ps1 b/SetPrinterComments.ps1
new file mode 100644
index 0000000..ad0a83d
--- /dev/null
+++ b/SetPrinterComments.ps1
@@ -0,0 +1,15 @@
+## Based off of a CSV, sets printer comments for a set of printers
+
+$Csv = Import-Csv "C:\Dropbox\Powershell\scripts\PrinterModelAndIPs.csv"
+$server_printers = Get-WMIObject -Class "Win32_Printer" -NameSpace "root\cimv2" -computername ctxps
+foreach ($row in $Csv) {
+ $model = $row.Model;
+ $ip = $row.IP;
+ $printer = $server_printers | ? {$_.comment -eq $ip }
+ if ($printer) {
+ #$printer | foreach {"Name: $($_.Name) | $($_.PortName) | $ip | $model"}
+ $printer | foreach { $_.Comment = "$ip $model"; $_.Put() | Out-Null}
+
+
+ }
+}
\ No newline at end of file
diff --git a/Start-CMApplicationDeploymentTest.ps1 b/Start-CMApplicationDeploymentTest.ps1
new file mode 100644
index 0000000..50bb1c0
--- /dev/null
+++ b/Start-CMApplicationDeploymentTest.ps1
@@ -0,0 +1,61 @@
+<#
+.SYNOPSIS
+
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename:
+ Credits:
+ Requirements:
+ Todos:
+.EXAMPLE
+
+.EXAMPLE
+
+.PARAMETER PARAM1
+
+.PARAMETER PARAM2
+
+#>
+[CmdletBinding(DefaultParameterSetName = 'name')]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [Parameter(ParameterSetName = 'name',
+ Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateSet("Tom","Dick","Jane")]
+ [ValidateRange(21,65)]
+ [ValidateScript({Test-Path $_ -PathType 'Container'})]
+ [ValidateNotNullOrEmpty()]
+ [ValidateCount(1,5)]
+ [ValidateLength(1,10)]
+ [ValidatePattern()]
+ [string]$Computername = 'DEFAULTVALUE'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+end {
+ try {
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Start-PostConfigmgrBackupTasks.ps1 b/Start-PostConfigmgrBackupTasks.ps1
new file mode 100644
index 0000000..e419300
--- /dev/null
+++ b/Start-PostConfigmgrBackupTasks.ps1
@@ -0,0 +1,270 @@
+#Requires -Version 3
+
+<#
+.SYNOPSIS
+ This script checks a ConfigMgr site backup to ensure success and performs various post-backup functions that
+ back up other critical data that the built-in ConfigMgr site backup task does not.
+.DESCRIPTION
+ This script checks any previous backup attempt ran within the last hour of this script running for a
+ a successful run if backup check is selected. It assumes you also have SSRS installed on the site DB server
+ and backs up both SSRS databases, exports out the SSRS encryption keys, backs up the export file and the
+ entire ReportingServices folder on the server. Once SSRS has been backed up, it then also copies the entire
+ SCCMContentLib folder, client install folder and the afterbackup.bat file to the destination backup folder path.
+ Once complete, it then attaches the log file it creates as part of the process and emails it out to the defined recipient.
+
+ The script intends on creating 7 days worth of backups in a central location labeled Monday through Friday
+ and places a copy of all backed up components in each day's folder.
+.NOTES
+ Created on: 6/13/2014
+ Created by: Adam Bertram
+ Filename: Start-PostConfigMgrBackupSteps.ps1
+ Credits: http://bit.ly/1i24NgC
+ Requirements: ConfigMgr, Reporting Point installed on the site DB server
+ Todos: Use the Sync framework to only copy deltas (http://bit.ly/1nh3FmP)
+ Backup custom updates added via SCUP
+ Verify copies were actually successful
+ Retrieve more params automatically
+.EXAMPLE
+ .\Start-PostConfigMgrBackupSteps.ps1
+ This example uses all default parameters for the script which will be the most likely way it is executed.
+.PARAMETER SiteCode
+ The ConfigMgr site code that the site server is a part of
+.PARAMETER SiteDbServer
+ The Configmgr site server that has the Reporting Services Point and the Site database server role installed.
+.PARAMETER DestDbBackupFolderPath
+ The UNC root folder path where the days' backup folders will be copied to
+.PARAMETER SrcReportingServicesFolderPath
+ The UNC folder path where you have installed reporting services to on the server.
+.PARAMETER ReportingServicesDbBackupSqlFilePath
+ The UNC file path to the SQL file that is dynamically created (if not exists) that the script passes to the
+ sqlcmd utility to kick off a backup of the SSRS databases. This does not have to exist. It is recommended
+ to allow the script to create this.
+.PARAMETER ReportingServicesEncKeyPassword
+ The password that's set on the exported SSRS keys
+.PARAMETER SrcContentLibraryFolderPath
+ The folder path to ConfigMgr's content library on the site server. This folder is called SCCMContentLib. The
+ default path probably does not need to be changed.
+.PARAMETER SrcClientInstallerFolderPath
+ The folder path where the ConfigMgr client install is located on the site server. This is backed up if you
+ have any hotfixes being installed with your clients and may be located in here.
+.PARAMETER SrcAfterBackupFilePath
+ The file path where the afterbackup.bat file is located. You should not have to change this from the default.
+.PARAMETER LogFilesFolderPath
+ The folder path where the script will create a log file for each day it runs.
+.PARAMETER CheckBackup
+ Use this switch to first check to ensure a recent backup was successful. This parameter is recommended
+ when running inside the afterbackup.bat file.
+#>
+[CmdletBinding()]
+param (
+ [string]$SiteCode = 'UHP',
+ [ValidateScript({Test-Connection $_ -Quiet -Count 1})]
+ [string]$SiteDbServer = 'CONFIGMANAGER',
+ [ValidateScript({ Test-Path $_ -PathType 'Container' })]
+ [string]$DestDbBackupFolderPath = '\\sanstoragea\lt_archive\30_Days\ConfigMgr',
+ [ValidateScript({ Test-Path $_ -PathType 'Container' })]
+ [string]$SrcReportingServicesFolderPath = "\\$SiteDbServer\f$\Sql2012Instance\MSRS11.MSSQLSERVER\Reporting Services",
+ [string]$ReportingServicesDbBackupSqlFilePath = "\\$SiteDbServer\c$\ReportingServicesDbBackup.sql",
+ [string]$ReportingServicesEncKeyPassword = 'my_password',
+ [ValidateScript({ Test-Path $_ -PathType 'Container' })]
+ [string]$SrcContentLibraryFolderPath = "\\$SiteDbServer\f$\SCCMContentLib",
+ [ValidateScript({ Test-Path $_ -PathType 'Container' })]
+ [string]$SrcClientInstallerFolderPath = "\\$SiteDbServer\c$\Program Files\Microsoft Configuration Manager\Client",
+ [ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
+ [string]$SrcAfterBackupFilePath = "\\$SiteDbServer\c$\Program Files\Microsoft Configuration Manager\inboxes\smsbkup.box\afterbackup.bat",
+ [string]$LogFilesFolderPath = "$DestDbBackupFolderPath\Logs",
+ [switch]$CheckBackup
+)
+
+begin {
+ Set-StrictMode -Version Latest
+ try {
+ ## This function builds a SQL file called $ReportingServicesDbBackupSqlFile that backs up both
+ ## reporting services databases to a subfolder called ReportsBackup under today's
+ ## destination backup folder
+ function New-ReportingServicesBackupSqlFile($TodayDbDestFolderPath) {
+ Add-Content -Value "declare @path1 varchar(100);
+ declare @path2 varchar(100);
+ SET @path1 = '$TodayDbDestFolderPath\ReportsBackup\ReportServer.bak';
+ SET @path2 = '$TodayDbDestFolderPath\ReportsBackup\ReportServerTempDB.bak';
+
+ USE ReportServer;
+ BACKUP DATABASE REPORTSERVER TO DISK = @path1;
+ BACKUP DATABASE REPORTSERVERTEMPDB TO DISK = @path2;
+ DBCC SHRINKFILE(ReportServer_log);
+ USE ReportServerTempDb;
+ DBCC SHRINKFILE(ReportServerTempDB_log);" -Path $ReportingServicesDbBackupSqlFilePath
+ }
+
+ function Convert-ToLocalFilePath($UncFilePath) {
+ $Split = $UncFilePath.Split('\')
+ $FileDrive = $Split[3].TrimEnd('$')
+ $Filename = $Split[-1]
+ $FolderPath = $Split[4..($Split.Length - 2)]
+ if ($Split.count -eq 5) {
+ "$FileDrive`:\$Filename"
+ } else {
+ "$FileDrive`:\$FolderPath\$Filename"
+ }
+ }
+
+ Function Get-LocalTime($UTCTime) {
+ $strCurrentTimeZone = (Get-WmiObject win32_timezone).StandardName
+ $TZ = [System.TimeZoneInfo]::FindSystemTimeZoneById($strCurrentTimeZone)
+ $LocalTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($UTCTime, $TZ)
+ $LocalTime
+ }
+
+ if (!(Test-Path $LogFilesFolderPath)) {
+ New-Item -Path $LogFilesFolderPath -Type Directory | Out-Null
+ }
+ $script:MyDate = Get-Date -Format 'MM-dd-yyyy'
+ $script:LogFilePath = "$LogFilesFolderPath\$MyDate.log"
+
+ ## Simple logging function to create a log file in $LogFilesFolderPath named today's
+ ## date then write a timestamp and the message on each line and outputs the log file
+ ## path it wrote to
+ function Write-Log($Message) {
+ $MyDateTime = Get-Date -Format 'MM-dd-yyyy H:mm:ss'
+ Add-Content -Path $script:LogFilePath -Value "$MyDateTime - $Message"
+ }
+
+ $DefaultBackupFolderPath = "$DestDbBackupFolderPath\$SiteCode" + 'Backup'
+ if (!(Test-Path $DefaultBackupFolderPath)) {
+ throw "Default backup folder path $DefaultBackupFolderPath does not exist"
+ }
+
+ if ($CheckBackup.IsPresent) {
+ ## Ensure the backup was successful before doing post-backup tasks
+
+ ## $DefaultBackupFolderPath is the path where the builtin Site Backup SQL maintenance task places the
+ ## backup. Ensure it has today's write time before going further because if not then the backup
+ ## didn't run successfully
+ $BackupFolderLastWriteDate = (Get-ItemProperty $DefaultBackupFolderPath).Lastwritetime.Date
+
+ $SuccessMessageId = 5035
+ $OneHourAgo = (Get-Date).AddHours(-1)
+ Write-Log "One hour ago detected as $OneHourAgo"
+
+ $WmiParams = @{
+ 'ComputerName' = $SiteDbServer;
+ 'Namespace' = "root\sms\site_$SiteCode";
+ 'Class' = 'SMS_StatusMessage';
+ 'Filter' = "Component = 'SMS_SITE_BACKUP' AND MessageId = '$SuccessMessageId'"
+ }
+ $LastSuccessfulBackup = (Get-WmiObject @WmiParams | sort time -Descending | select -first 1 @{ n = 'DateTime'; e = { $_.ConvertToDateTime($_.Time) } }).DateTime
+ $LastSuccessfulBackup = Get-LocalTime $LastSuccessfulBackup
+ Write-Log "Last successful backup detected on $LastSuccessfulBackup"
+ $IsBackupSuccessful = $LastSuccessfulBackup -gt $OneHourAgo
+
+ if (($BackupFolderLastWriteDate -ne (get-date).date) -or !$IsBackupSuccessful) {
+ throw 'The backup was not successful. Post-backup procedures not necessary'
+ }
+ }
+
+ $CommonCopyFolderParams = @{
+ 'Recurse' = $true;
+ 'Force' = $true;
+ }
+
+ } catch {
+ Write-Log "ERROR: $($_.Exception.Message)"
+ exit (10)
+ }
+}
+
+process {
+ try {
+ ## If today's folder exists in the root of the backup folder path
+ ## remove it else create a new one
+ $Today = (Get-Date).DayOfWeek
+ $TodayDbDestFolderPath = "$DestDbBackupFolderPath\$Today"
+ if ((Test-Path $TodayDbDestFolderPath -PathType 'Container')) {
+ Remove-Item $TodayDbDestFolderPath -Force -Recurse
+ Write-Log "Removed $TodayDbDestFolderPath..."
+ }
+
+ ## Rename the default backup folder to today's day of the week
+ Rename-Item $DefaultBackupFolderPath $Today
+ ## Create the folder to put the reporting services database backups in
+ New-Item -Path "$TodayDbDestFolderPath\ReportsBackup" -ItemType Directory | Out-Null
+
+ ## if the SQL file that gets invoked to backup the SSRS databases isn't in the root of
+ ## C on the site server, build it. The root of C isn't necessary. It just needs to be
+ ## somewhere on the local server
+ if (Test-Path $ReportingServicesDbBackupSqlFilePath) {
+ Remove-Item $ReportingServicesDbBackupSqlFilePath -Force
+ }
+ New-ReportingServicesBackupSqlFile $TodayDbDestFolderPath
+ Write-Log "Created new SQL file in $TodayDbDestFolderPath..."
+
+ ## Convert the UNC path specified for the SQL file into a local path to feed to
+ ## sqlcmd on the site server which backs up the SSRS databases. Confirm success
+ ## afterwards.
+ Write-Log "Backing up SSRS Databases..."
+ $LocalPath = Convert-ToLocalFilePath $ReportingServicesDbBackupSqlFilePath
+ $result = Invoke-Command -ComputerName $SiteDbServer -ScriptBlock { sqlcmd -i $using:LocalPath }
+ if ($result[-1] -match 'DBCC execution completed') {
+ Write-Log 'Successfully backed up SSRS databases'
+ } else {
+ Write-Log 'WARNING: Failed to backup SSRS databases'
+ }
+
+ ## Export the SSRS Encryption keys to a local file via remoting on the site server and
+ ## copy that file to the backup location
+ Write-Log "Exporting SSRS encryption keys..."
+ $ExportFilePath = "\\$SiteDbServer\c$\rsdbkey.snk"
+ $LocalPath = Convert-ToLocalFilePath $ExportFilePath
+ $result = Invoke-Command -ComputerName $SiteDbServer -ScriptBlock { echo y | rskeymgmt -e -f $using:LocalPath -p $using:ReportingServicesEncKeyPassword }
+ if ($result[-1] -ne 'The command completed successfully') {
+ Write-Log 'WARNING: SSRS keys were not exported!'
+ } else {
+ Copy-Item $ExportFilePath $TodayDbDestFolderPath -Force
+ Write-Log 'Successfully exported and backed up encryption keys.'
+ }
+
+ ## Backup the Reporting Services SSRS folder
+ Write-Log "Backing up $SrcReportingServicesFolderPath..."
+ Copy-Item @CommonCopyFolderParams -Path $SrcReportingServicesFolderPath -Destination "$TodayDbDestFolderPath\ReportsBackup"
+ Write-Log "Successfully backed up the $SrcReportingServicesFolderPath folder.."
+
+ ## Backup the SCCMContentLib folder
+ Write-Log "Backing up $SrcContentLibraryFolderPath..."
+ Copy-Item @CommonCopyFolderParams -Path $SrcContentLibraryFolderPath -Destination $TodayDbDestFolderPath
+ Write-Log "Successfully backed up the $SrcContentLibraryFolderPath folder.."
+
+ ## Backup the client install folder from the site server to the backup folder
+ Write-Log "Backing up $SrcClientInstallerFolderPath..."
+ Copy-Item @CommonCopyFolderParams -Path $SrcClientInstallerFolderPath -Destination $TodayDbDestFolderPath
+ Write-Log "Successfully backed up the $SrcClientInstallerFolderPath folder.."
+
+ ##TODO: Backup any SCUP updates
+ ## On the computer that runs Updates Publisher, browse the Updates Publisher 2011 database file (Scupdb.sdf)
+ ## in %USERPROFILE%\AppData\Local\Microsoft\System Center Updates Publisher 2011\5.00.1727.0000\. There is
+ ## a different database file for each user that runs Updates Publisher 2011. Copy the database file to your
+ ## backup destination. For example, if your backup destination is E:\ConfigMgr_Backup, you could copy the
+ ## Updates Publisher 2011 database file to E:\ConfigMgr_Backup\SCUP2011.
+
+ ## Backup the afterbackup.bat file that kicks off this script
+ Write-Log "Backing up $SrcAfterBackupFilePath.."
+ Copy-Item @CommonCopyFolderParams -Path $SrcAfterBackupFilePath -Destination $TodayDbDestFolderPath
+ Write-Log "Successfully backed up the $SrcAfterBackupFilePath file..."
+
+ } catch {
+ Write-Log "ERROR: $($_.Exception.Message)"
+ }
+}
+
+end {
+ Write-Log 'Emailing results of backup...'
+ ## Email me the results of the backup and post-backup tasks
+ $Params = @{
+ 'From' = 'UNH ConfigMgr Backup ';
+ 'To' = 'Adam Bertram ';
+ 'Subject' = 'UNH ConfigMgr Backup';
+ 'Attachment' = $script:LogFilePath;
+ 'SmtpServer' = 'smtp.uhhg.org'
+ }
+
+ Send-MailMessage @Params -Body 'ConfigMgr Backup Email'
+}
\ No newline at end of file
diff --git a/Sync-CmToWsusSoftwareUpdates.ps1 b/Sync-CmToWsusSoftwareUpdates.ps1
new file mode 100644
index 0000000..7ffba1f
--- /dev/null
+++ b/Sync-CmToWsusSoftwareUpdates.ps1
@@ -0,0 +1,235 @@
+<#
+.SYNOPSIS
+ This script retrieves all Configuration Manager updates that are deployed. It will then check
+ a WSUS server's updates and approve any WSUS updates that are in Configuration Manager. Optionally,
+ it can also decline any WSUS updates that are not in Configuration Manager as well as force a manual
+ MS Update --> WSUS sync if it finds any updates in Configuration Manager that are not in WSUS.
+.NOTES
+ Created on: 1/29/15
+ Created by: Adam Bertram
+ Filename: Sync-CmToWsusSoftwareUpdates.ps1
+ Credits: http://blogs.technet.com/b/deploymentguys/archive/2009/10/22/approving-windows-updates-in-an-mdt-2010-standalone-environment-from-a-configmgr-software-update-point.aspx
+ http://configmgrblog.com/2013/10/26/migrate-approved-software-updates-wsus-configmgr-2012/
+ http://myitforum.com/cs2/blogs/maikkoster/archive/2013/08/29/approving-updates-from-configmgr-scup-to-wsus.aspx
+ Requirements: The WSUS console must be installed - http://www.microsoft.com/en-us/download/details.aspx?id=5216
+ Suggestions: WSUS: No updates are manually approved on the WSUS server
+ WSUS: Update source set to Microsoft Update
+ WSUS: Synchronization schedule is set to automatic
+ WSUS: Automatic Approvals are disabled
+ This script is meant to be ran as a scheduled task and as a right-click tool in ConfigMgr.
+.EXAMPLE
+ PS> .\Sync-CmToWsusSoftwareUpdates.ps1
+
+ This example finds all deployed CM updates and attempts to match these updates against WSUS. If a match is found, it will
+ approve the WSUS update.
+.EXAMPLE
+ PS> .\Sync-CmToWsusSoftwareUpdates.ps1 -DeclineAllNonMatches
+
+ This example finds all deployed CM updates and attempts to match these updates against WSUS. If a match is found, it will
+ approve the WSUS update. If a WSUS update is not in CM, it will decline it.
+.PARAMETER DeclineAllNonMatches
+ By default, the script will only approve matches in WSUS. If this switch parameter is used, it will also decline all
+ WSUS updates that do not have a match in CM.
+.PARAMETER SyncWsus
+ By default, the script will report on any CM updates that it could not find a match to in WSUS. If this switch paramter is used
+ it will force a manual sync on the WSUS server from MS update in an effort to get any remaining updates onto the WSUS server.
+.PARAMETER LogFilePath
+ The file path where you'd like to report the script's activity
+.PARAMETER CmSiteServer
+ The name of the site server to query for updates.
+.PARAMETER CmSiteCode
+ The 3-letter ConfigMgr site code
+.PARAMETER WsusServer
+ The name of the WSUS server
+.PARAMETER WsusServerPort
+ This is the port number that the WSUS server is listening on HTTP. It's typically 80 or 8530.
+#>
+[CmdletBinding()]
+param (
+ [switch]$DeclineAllNonMatches,
+ [switch]$SyncWsus,
+ [string]$LogFilePath = "$PsScriptRoot\SCCM-WSUSUpdateSync.log",
+ [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1})]
+ [string]$CmSiteServer = '',
+ [string]$CmSiteCode = '',
+ [ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
+ [string]$WsusServer = '',
+ [string]$WsusServerPort = '8530'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+
+ ## These functions would be nice to have but I could not immediately figure out how to find
+ ## the approved products and classifications in CM to sync with WSUS
+ <#function Sync-ClassificationSyncOptions {
+ Invoke-Command -ComputerName $WsusServer -ScriptBlock { Set-WsusClassification }
+ }
+
+ function Sync-ProductSyncOptions {
+ Invoke-Command -ComputerName $WsusServer -ScriptBlock { Set-WsusProduct }
+ }#>
+
+ function Write-Log {
+ <#
+ .SYNOPSIS
+ This function creates or appends a line to a log file
+
+ .DESCRIPTION
+ This function writes a log line to a log file
+ .PARAMETER Message
+ The message parameter is the log message you'd like to record to the log file
+ .PARAMETER LogLevel
+ The logging level is the severity rating for the message you're recording.
+ You have 3 severity levels available; 1, 2 and 3 from informational messages
+ for FYI to critical messages. This defaults to 1.
+
+ .EXAMPLE
+ PS C:\> Write-Log -Message 'Value1' -LogLevel 'Value2'
+
+ This example shows how to call the Write-Log function with named parameters.
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [string]$Message,
+ [ValidateSet(1, 2, 3)]
+ [int]$LogLevel = 1
+ )
+
+ try {
+ [pscustomobject]@{
+ 'Time' = Get-Date
+ 'Message' = $Message
+ 'ScriptLineNumber' = "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)"
+ 'Severity' = $LogLevel
+ } | Export-Csv -Path $LogFilePath -Append -NoTypeInformation
+ } catch {
+ Write-Error $_.Exception.Message
+ $false
+ }
+ }
+
+ function Get-MyWsusUpdate {
+ $Wsus.GetUpdates()
+ }
+
+ function Get-AllComputerTargetGroup {
+ $Groups = $Wsus.GetComputerTargetGroups()
+ $Groups | where { $_.Name -eq 'All Computers' }
+ }
+
+ function Approve-MyWsusUpdate ([Microsoft.UpdateServices.Internal.BaseApi.Update]$Update) {
+ $AllComputerTg = Get-AllComputerTargetGroup
+ $Update.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,$AllComputerTg) | Out-Null
+ }
+
+ function Decline-MyWsusUpdate ([Microsoft.UpdateServices.Internal.BaseApi.Update]$Update) {
+ $Update.Decline()
+ }
+
+ function Sync-WsusServer {
+ $Subscription = $Wsus.GetSubscription()
+ $Subscription.StartSynchronization()
+ }
+
+ try {
+ Write-Log 'Loading the WSUS type and creating the WSUS server object...'
+ ## Load the type in order to query updates from the WSUS server. You must have the
+ ## WSUS administration console installed to get these assemblies
+ [void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
+ $script:Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($WsusServer, $false, $WsusServerPort)
+ } catch {
+ Write-Log "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)" -LogLevel '3'
+ }
+
+}
+process {
+ try {
+ Write-Log 'Finding all deployed CM updates...'
+ $DeployedCmUpdates = Get-CimInstance -ComputerName $CmSiteServer -Namespace "root\sms\site_$CmSiteCode" -Class SMS_SoftwareUpdate | where { $_.IsDeployed -and !$_.IsSuperseded -and !$_.IsExpired }
+ if (!$DeployedCmUpdates) {
+ throw 'Error retrieving CM updates'
+ } else {
+ Write-Log "Found $($DeployedCmUpdates.Count) deployed updates"
+ }
+ Write-Log "Finding all WSUS updates on the $WsusServer WSUS server..."
+ $WsusUpdates = Get-MyWsusUpdate
+ if (!$WsusUpdates) {
+ throw 'Error retrieving WSUS updates'
+ }
+ Write-Log "Found $($WsusUpdates.Count) applicable updates on the WSUS server"
+ Write-Log 'Beginning matching process...'
+ $MatchesMade = 0
+ $NoMatchMade = 0
+ $ApprovedMatches = 0
+ $AlreadyApprovedMatches = 0
+ $DeclinedWsusUpdates = 0
+ foreach ($WsusUpdate in $WsusUpdates) {
+ try {
+ #Write-Log "Checking WSUS update $($WsusUpdate.Title) for a match..."
+ if ($DeployedCmUpdates.LocalizedDisplayname -contains $WsusUpdate.Title) {
+ #Write-Log "Found matching WSUS update '$($WsusUpdate.Title)'"
+ $MatchesMade++
+ if (!$WsusUpdate.IsApproved) {
+ #Write-Log "Update is not approved. Checking for license agreement"
+ $ApprovedMatches++
+ if ($WsusUpdate.HasLicenseAgreement) {
+ #Write-Log "Update has a license agreement. Accepting..."
+ $WsusUpdate.AcceptLicenseAgreement()
+ } else {
+ #Write-Log 'Update does not have a license agreement'
+ }
+ #Write-Log "Approving WSUS update..."
+ Approve-MyWsusUpdate -Update $WsusUpdate
+ $ApprovedMatches++
+ } else {
+ #Write-Log "WSUS update is already approved."
+ $AlreadyApprovedMatches++
+ }
+ } else {
+ #Write-Log 'No match found'
+ $NoMatchMade++
+ if ($DeclineAllNonMatches.IsPresent) {
+ if ($WsusUpdate.IsDeclined) {
+ #Write-Log 'The WSUS update is already declined. No need to decline.'
+ } else {
+ #Write-Log 'Declining WSUS update...'
+ $DeclinedWsusUpdates++
+ Decline-MyWsusUpdate -Update $WsusUpdate
+ }
+ }
+ }
+ } catch {
+ Write-Log "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)" -LogLevel '3'
+ }
+ }
+ Write-Log 'Finding all CM updates that are not in WSUS...'
+ $CmUpdatesNotInWsus = $DeployedCmUpdates | where { $WsusUpdates.Title -notcontains $_.LocalizedDisplayName }
+ if (!$CmUpdatesNotInWsus) {
+ Write-Log 'No CM updates found with no match in WSUS'
+ } else {
+ foreach ($CmUpdate in $CmUpdatesNotInWsus) {
+ Write-Log "CM update '$($CmUpdate.LocalizedDisplayName)' not in WSUS"
+ }
+ if ($SyncWsus.IsPresent) {
+ ## Force a manual sync with Microsoft in an attempt to download the updates we're missing. We're
+ ## not tracking the sync but hopefully by the next CM --> WSUS sync, these updates will be there.
+ Write-Log 'Forcing a WSUS sync...'
+ Sync-WsusServer
+ }
+ }
+ Write-Log "---------------------------------------------"
+ Write-Log "WSUS Updates in CM: $MatchesMade"
+ Write-Log "WSUS Updates in CM Declined: $DeclinedWsusUpdates"
+ Write-Log "WSUS Updates in CM Already Approved: $AlreadyApprovedMatches"
+ Write-Log "WSUS Updates in CM Approved: $ApprovedMatches"
+ Write-Log "WSUS Updates not in CM: $NoMatchMade"
+ Write-Log "CM Updates not in WSUS: $($CmUpdatesNotInWsus.Count)"
+ Write-Log "---------------------------------------------"
+ Write-Log 'CM --> WSUS synchronization complete'
+ } catch {
+ Write-Log "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)" -LogLevel '3'
+ }
+}
\ No newline at end of file
diff --git a/Sync-CsvToSql.ps1 b/Sync-CsvToSql.ps1
new file mode 100644
index 0000000..fb735b2
--- /dev/null
+++ b/Sync-CsvToSql.ps1
@@ -0,0 +1,126 @@
+#Requires -Module SQLPSX
+
+<#
+.SYNOPSIS
+ This queries compares the contents of a CSV file against a SQL database table
+ and either reports or syncs the different field values to the SQL table.
+
+ If -replace is not used, it will output only the rows in the SQL database that are
+ different
+
+ 2/16/15 CURRENT LIMITATION: This will only sync int, smallint, varchar and char fields.
+.NOTES
+ Created on: 2/16/15
+ Created by: Adam Bertram
+ Filename: Sync-CsvToSql.ps1
+.EXAMPLE
+
+.EXAMPLE
+.PARAMETER Replace
+ Use this switch parameter to move from simply reporting on changes to making changes
+.PARAMETER CsvFilePath
+ The path to the CSV file that will be read and compared against the SQL table
+.PARAMETER ServerInstance
+ The server name and the instance name (if more than one installed) of the SQL server
+.PARAMETER Database
+ The name of the database that contains the table you'll be syncing
+.PARAMETER Schema
+ The database schema if other than dbo.
+.PARAMETER Table
+ The name of the SQL table you'll be syncing the CSV's contents to
+#>
+[CmdletBinding()]
+param (
+ [switch]$Replace,
+ [Parameter(Mandatory)]
+ [ValidateScript({ Test-Path $_ -PathType Leaf })]
+ [string]$CsvFilePath,
+ [Parameter(Mandatory)]
+ [string]$ServerInstance,
+ [Parameter(Mandatory)]
+ [string]$Database,
+ [string]$Schema = 'dbo',
+ [Parameter(Mandatory)]
+ [string]$Table
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ ## Ensure the columns in the CSV file and the database are equal
+ $CsvRows = Import-Csv -Path $CsvFilePath
+ $SqlTable = Get-SqlTable -Database (Get-SqlDatabase -sqlserver $Server -dbname $Database) -Name $Table -Schema $Schema
+ if (Compare-Object -DifferenceObject ($CsvRows[0].Psobject.Properties.Name) -ReferenceObject ($SqlTable.Columns.Name)) {
+ throw 'The field names in the CSV file and the SQL database are not equal'
+ }
+
+ ## Load the entire table in memory for faster querying
+ $SqlRows = Get-SqlData -sqlserver $ServerInstance -dbname $Database -qry "SELECT * FROM $Table"
+ } catch {
+ Write-Error $_.Exception.Message
+ exit
+ }
+}
+
+process {
+ try {
+
+ ## Find the primary key to keep a 1:1 match
+ $PrimaryKey = ($SqlTable.Columns | Where-Object { $_.InPrimaryKey }).Name
+ ## I must separate the int and char columns out because if a string is detected in the CSV and that value
+ ## is attempted to be updated in the table in a non-char field, it will fail. This allows me
+ ## to treat the value as a string with quotes or not.
+ $SqlIntCols = $SqlTable.Columns | where { @('smallint', 'int') -contains $_.DataType.Name }
+ $SqlCharCols = $SqlTable.Columns | where { @('varchar', 'char') -contains $_.DataType.Name }
+
+ foreach ($CsvRow in $CsvRows) {
+ try {
+ ## Find the match
+ $SqlRow = $SqlRows | Where-Object { $_.$PrimaryKey -eq $CsvRow.$PrimaryKey }
+ if ($SqlRow) {
+ #Write-Verbose "SQL row match found for row $($CsvRow.$PrimaryKey)"
+ #$FieldDiffs = [System.Collections.ArrayList]@()
+ $FieldDiffs = [ordered]@{ }
+ foreach ($CsvProp in ($CsvRow.PsObject.Properties | where { $_.Name -ne $PrimaryKey })) {
+ foreach ($SqlProp in ($SqlRow.PsObject.Properties | where { $_.Name -ne $PrimaryKey })) {
+ if (($CsvProp.Name -eq $SqlProp.Name) -and ($CsvProp.Value -ne $SqlProp.Value)) {
+ $FieldDiffs["$($CsvProp.Name) - FROM"] = $SqlProp.Value;
+ $FieldDiffs["$($CsvProp.Name) - TO"] = $CsvProp.Value
+ }
+ }
+ }
+ if (!($FieldDiffs.Keys | where { $_ })) {
+ Write-Verbose "All fields are equal for row $($SqlRow.$PrimaryKey)"
+ } else {
+ $FieldDiffs['PrimaryKeyValue'] = $SqlRow.$PrimaryKey
+ if (!$Replace.IsPresent) {
+ [pscustomobject]$FieldDiffs
+ } else {
+ # $UpdateString = 'UPDATE Directory SET '
+ # foreach ($Diff in $FieldDiffs) {
+ # if ($Replace.IsPresent) {
+ # if ($SqlIntCols.Name -contains $Prop.Name) {
+ # $Items.Add("$($Prop.Name)=$($Prop.Value)") | Out-Null
+ # } elseif ($SqlCharCols.Name -contains $Prop.Name) {
+ # $Items.Add("$($Prop.Name)='$($Prop.Value)'") | Out-Null
+ # }
+ # $UpdateString += ($Items -join ',')
+ # $UpdateString += " WHERE $PrimaryKey = '$($CsvRow.$PrimaryKey)'"
+ # Set-SqlData @SqlParams -qry $UpdateString
+ # } else {
+ # $Fiel
+ # }
+ }
+ }
+ } else {
+ Write-Verbose "No SQL row match found for CSV row $($CsvRow.$PrimaryKey)"
+ }
+ } catch {
+ Write-Warning "Error Occurred: $($_.Exception.Message) in row $($CsvRow.$PrimaryKey)"
+ }
+ }
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Test-ClientDynamicDns.ps1 b/Test-ClientDynamicDns.ps1
new file mode 100644
index 0000000..4e40766
--- /dev/null
+++ b/Test-ClientDynamicDns.ps1
@@ -0,0 +1,201 @@
+#Requires -Module DnsServer
+#Requires -Version 3
+
+<#
+.SYNOPSIS
+ This script tests a particular computer to see if it's dynamic DNS functionality is working.
+.DESCRIPTION
+ This script firsts check to ensure the any NIC on the specified computer has dynamic DNS enabled. If a NIC is found, it then proceeds
+ to find the client's primary DNS server. This will be the DNS server the script calls for when checking server-side to see if
+ it can update it's own record. It then finds the client's primary DNS suffix. This will be the zone the script checks on
+ the server.
+
+ If the computer record's timestamp is still within the no-refresh period of the zone, the test cannot happen because the server
+ will disallow any updates to the timestamp. However, the script will still return true because the record is still considered "healthy".
+
+ It then tries to issue an "ipconfig /registerdns" on the computer via remoting. If that fails, it then attempts to create a process
+ via WMI. It then waits a default of 10 seconds and checks the computer's DNS record at the server to see if the register DNS attempt
+ worked. If, after the retry interval time elapses, the record's timestamp still hasn't been updated, the script will return a failure.
+.NOTES
+ Created on: 8/19/2014
+ Created by: Adam Bertram
+ Filename: Test-ClientDynamicDns.ps1
+.EXAMPLE
+ PS> .\Test-ClientDynamicDns.ps1 -Computername COMPUTER1 -RetryInterval 20
+
+ In this example, the script will test the computername COMPUTER1 and will retry 20 times every second to see if the
+ client computer updated it's timestamp on it's DNS host record.
+.EXAMPLE
+ PS> .\Test-ClientDynamicDns.ps1 -Computername COMPUTER1
+
+ In this example, the script will test the computername COMPUTER1 and will retry 10 times every second to see if the
+ client computer updated it's timestamp on it's DNS host record.
+.PARAMETER Computername
+ The name of the computer you'd like to test dynamic DNS functionality on
+.PARAMETER RetryInterval
+ The total time you'd like the script to check the computer's host DNS record for a current timestamp
+#>
+[CmdletBinding()]
+[OutputType('System.Management.Automation.PSCustomObject')]
+param (
+ [Parameter(Mandatory,
+ ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [ValidateScript({Test-Connection -ComputerName $_ -Quiet -Count 1})]
+ [string]$Computername,
+ [Parameter()]
+ [int]$RetryInterval = 10
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+ try {
+ ## http://www.leeholmes.com/blog/2009/11/20/testing-for-powershell-remoting-test-psremoting/
+ function Test-PsRemoting {
+ param (
+ [Parameter(Mandatory = $true)]
+ $computername
+ )
+
+ try {
+ Write-Verbose "Testing for enabled remoting"
+ $result = Invoke-Command -ComputerName $computername { 1 }
+ } catch {
+ return $false
+ }
+
+ ## I’ve never seen this happen, but if you want to be
+ ## thorough….
+ if ($result -ne 1) {
+ Write-Verbose "Remoting to $computerName returned an unexpected result."
+ return $false
+ }
+ $true
+ }
+
+ function Get-ClientPrimaryDns ($NicIndex) {
+ Write-Verbose "Finding primary DNS server for client '$Computername'"
+ $Result = Get-WmiObject -ComputerName $Computername -Class win32_networkadapterconfiguration -Filter "IPenabled = $true AND Index = $NicIndex"
+ if ($Result) {
+ $PrimaryDnsServer = $Result.DNSServerSearchOrder[0]
+ Write-Verbose "Found computer '$Computername' primary DNS server as '$PrimaryDnsServer'"
+ $PrimaryDnsServer
+ } else {
+ $false
+ }
+ }
+
+ function Get-ClientPrimaryDnsSuffix {
+ $Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computername)
+ $RegistryKey = $Registry.OpenSubKey("SYSTEM\CurrentControlSet\Services\Tcpip\Parameters", $true)
+ $DnsSuffix = $RegistryKey.GetValue('NV Domain')
+ if ($DnsSuffix) {
+ Write-Verbose "Computer '$Computername' primary DNS suffix is '$DnsSuffix'"
+ $DnsSuffix
+ } else {
+ Write-Warning "Could not find primary DNS suffix on computer '$Computername'"
+ $false
+ }
+ }
+
+ function Get-DynamicDnsEnabledNicIndex {
+ $EnabledIndex = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -ComputerName $Computername -Filter { IPEnabled = 'True' } | where { $_.FullDNSRegistrationEnabled }
+ if (!$EnabledIndex) {
+ Write-Warning 'No NIC detected to have dynamic DNS enabled'
+ $false
+ } elseif ($EnabledIndex -is [array]) {
+ Write-Warning 'Multiple NICs detected having dynamic DNS enabled. This is not supported'
+ $false
+ } else {
+ Write-Verbose "Found NIC with index '$($EnabledIndex.Index)' as dynamic DNS enabled"
+ [int]$EnabledIndex.Index
+ }
+ }
+
+ function Validate-IsInRefreshPeriod($Record) {
+ if ($Record.Timestamp.AddDays($ZoneAging.NoRefreshInterval.Days) -lt (Get-Date)) {
+ Write-Verbose 'The record is in the refresh period'
+ $true
+ } else {
+ Write-Verbose 'The record is not in the refresh period'
+ $false
+ }
+ }
+
+ $ResultHash = @{ 'Computername' = $Computername; }
+
+ $EnabledNicIndex = Get-DynamicDnsEnabledNicIndex
+ if ($EnabledNicIndex -isnot [int]) {
+ throw "Computer '$Computername' does not have dynamic DNS enabled on any interface or on more than 1 interface"
+ exit
+ }
+
+ $PrimaryDnsServer = Get-ClientPrimaryDns $EnabledNicIndex
+ if (! $PrimaryDnsServer) {
+ throw "Could not find computer '$Computername' primary DNS server."
+ exit
+ }
+ $DnsZone = Get-ClientPrimaryDnsSuffix
+ if (! $DnsZone) {
+ throw "Could not find computer '$Computername' primary DNS suffix."
+ exit
+ }
+ $script:ZoneAging = Get-DnsServerZoneAging -Name $DnsZone -ComputerName $PrimaryDnsServer
+
+ $Record = Get-DnsServerResourceRecord -ComputerName $PrimaryDnsServer -Name $Computername -RRType A -ZoneName $DnsZone -ea silentlycontinue
+ if ($Record -and !($Record.TimeStamp)) {
+ throw "The '$($Record.Hostname)' record is static and has no timestamp."
+ } elseif (!$Record) {
+ Write-Verbose "The '$Computername' record does not exist on the DNS server '$($PrimaryDnsServer)'."
+ } elseif (!(Validate-IsInRefreshPeriod $Record)) {
+ Write-Verbose "The '$($Record.Hostname)' record timestamp is still within the '$DnsZone' zone no-refresh period."
+ $ResultHash.Result = $true
+ [pscustomobject]$ResultHash
+ exit
+ }
+} catch {
+ Write-Error $_.Exception.Message
+ break
+}
+}
+
+process {
+ try {
+ ## Need to round the time down to the nearest hour. This is because when the DNS record's timestamp is updated it will
+ ## always do this.
+ $NowRoundHourDown = ((Get-Date).Date).AddHours((Get-Date).Hour)
+ if (Test-PsRemoting $Computername) {
+ Write-Verbose "Remoting already enabled on $Computername"
+ Invoke-Command -ComputerName $Computername -ScriptBlock { ipconfig /registerdns } | Out-Null
+ } else {
+ Write-Warning "Remoting not enabled on $Computername. Will attempt to use WMI to create remote process"
+ if (([WMICLASS]"\\$Computername\Root\CIMV2:Win32_Process").create("ipconfig /registerdns").ReturnValue -ne 0) {
+ throw "Unable to successfully start remote process on '$Computername'"
+ }
+ }
+ Write-Verbose "Initiated DNS record registration on '$Computername'. Waiting for record to update on DNS server.."
+ ## Wait at least 5 seconds before even starting to give the record a chance to update.
+ Start-Sleep -Seconds 5
+ for ($i = 0; $i -lt $RetryInterval; $i++) {
+ $Record = Get-DnsServerResourceRecord -ComputerName $PrimaryDnsServer -Name $Computername -RRType A -ZoneName $DnsZone -ea SilentlyContinue
+ if ($Record) {
+ $Timestamp = $Record.Timestamp
+ }
+ if ($Timestamp -eq $NowRoundHourDown) {
+ Write-Verbose "Host DNS record for '$Computername' matches current rounded time of $NowRoundHourDown"
+ $ResultHash.Result = $true
+ [pscustomobject]$ResultHash
+ exit
+ } else {
+ Write-Verbose "Host DNS record timestamp '$Timestamp' for '$Computername' does not match current rounded time of '$NowRoundHourDown'. Trying again..."
+ }
+ Start-Sleep -Seconds 1
+ }
+
+ $ResultHash.Result = $false
+ [pscustomobject]$ResultHash
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
\ No newline at end of file
diff --git a/Test-CmDeploymentClientStatus.ps1 b/Test-CmDeploymentClientStatus.ps1
new file mode 100644
index 0000000..8d33883
--- /dev/null
+++ b/Test-CmDeploymentClientStatus.ps1
@@ -0,0 +1,149 @@
+#Requires -Module ConfigurationManager
+#Requires -Version 3
+
+<#
+.SYNOPSIS
+ This script checks a SCCM deployment for the number of clients that have failed and if the deployment
+ has not started. If either is true then it will send a notification.
+.NOTES
+ Created on: 8/22/2014
+ Created by: Adam Bertram
+ Filename: Test-CmDeploymentClientStatus.ps1
+.EXAMPLE
+ PS> Test-CmDeploymentClientStatus -DeploymentID ABC0045G
+
+ This example would get the percentage of failed client for deployment ID ABC0045G and if
+ the percentage was equal to or over the FailureThreshold it would send an email to
+ ToEmailAddress.
+.PARAMETER DeploymentId
+ The SCCM deployment ID to check
+.PARAMETER FailurePercentThreshold
+ The percentage of clients that must fail the deployment before an email is sent
+.PARAMETER ToEmailAddress
+ The email address to send a notification email to (if necessary)
+.PARAMETER FromEmailAddress
+ The email address to show as being sent from
+.PARAMETER FromDisplayName
+ The name shown in most email clients as the email being sent from
+.PARAMETER EmailSubject
+ The subject of the email
+.PARAMETER EmailBody
+ If you'd like to include a snippet of text in the email body. Use this parameter.
+.PARAMETER SmtpServer
+ The SMTP server to send the email through
+.PARAMETER SiteServer
+ The name of the SCCM site server
+.PARAMETER SiteCode
+ The SCCM site code
+
+#>
+[CmdletBinding(DefaultParameterSetName = 'EmailNotification')]
+param (
+ [Parameter(Mandatory)]
+ [string]$DeploymentId,
+ [Parameter()]
+ [ValidateRange(1, 100)]
+ [int]$FailurePercentThreshold = 10,
+ [Parameter(ParameterSetName = 'EmailNotification')]
+ [string]$ToEmailAddress = 'replacethis@defaultemail.com',
+ [Parameter(ParameterSetName = 'EmailNotification')]
+ [string]$FromEmailAddress = 'Replace me',
+ [Parameter(ParameterSetName = 'EmailNotification')]
+ [string]$FromDisplayName = 'A Failed SCCM Deployment',
+ [Parameter(ParameterSetName = 'EmailNotification')]
+ [string]$EmailSubject = 'Failed SCCM Deployment',
+ [Parameter(ParameterSetName = 'EmailNotification')]
+ [string]$EmailBody,
+ [Parameter(ParameterSetName = 'EmailNotification')]
+ [string]$SmtpServer = 'replace.this.com',
+ [Parameter()]
+ [string]$SiteCode = 'UHP'
+)
+
+begin {
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+ Set-StrictMode -Version Latest
+
+ function Send-FailNotification ($Method) {
+ switch ($Method) {
+ 'Email' {
+ $Params = @{
+ 'From' = "$FromDisplayName <$FromEmailAddress>"
+ 'To' = $ToEmailAddress
+ 'Subject' = $EmailSubject
+ 'SmtpServer' = $SmtpServer
+ }
+ if (!$EmailBody) {
+ $Params.Body = "$($Summary.PercentageFailed) percent of clients failed deployment ID $DeploymentId`n`r$Summary"
+ } else {
+ $Params.Body = $EmailBody
+ }
+ Send-MailMessage @Params
+ }
+ default {
+ $false
+ }
+ }
+ }
+
+ function Get-ClientPercentage ($ClientGroup) {
+ [math]::Round((($Deployment.$ClientGroup / $Deployment.NumberTargeted) * 100), 1)
+ }
+
+ function Get-ClientStatusSummary {
+ $script:Deployment = Get-CMDeployment -DeploymentId $DeploymentId
+ if (!$Deployment) {
+ $false
+ } else {
+ $Object = @{
+ 'NumberTargeted' = $Deployment.NumberTargeted
+ 'NumberUnknown' = $Deployment.NumberUnknown
+ 'PercentageFailed' = Get-ClientPercentage 'NumberErrors'
+ 'PercentageInProgress' = Get-ClientPercentage 'NumberInProgress'
+ 'PercentageOther' = Get-ClientPercentage 'NumberOther'
+ 'PercentageSuccess' = Get-ClientPercentage 'NumberSuccess'
+ 'PercentageUnknown' = Get-ClientPercentage 'NumberUnknown'
+ }
+ [pscustomobject]$Object
+ }
+ }
+
+ try {
+ Write-Verbose 'Checking to see if the SCCM module is available...'
+ if (!(Test-Path "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1")) {
+ throw 'Configuration Manager module not found. Is the admin console intalled?'
+ } elseif (!(Get-Module 'ConfigurationManager')) {
+ Write-Verbose 'The SCCM module IS available.'
+ Import-Module "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1"
+ }
+ $Location = (Get-Location).Path
+ Set-Location "$($SiteCode):"
+
+ } catch {
+ Write-Error $_.Exception.Message
+ }
+}
+
+process {
+ try {
+ Write-Verbose "Getting a summary of all client activity for deployment ID $DeploymentId..."
+ $script:Summary = Get-ClientStatusSummary
+ if (!$Summary) {
+ throw "Could not find deployment ID '$DeploymentId'"
+ } elseif ($Summary.PercentageFailed -ge $FailurePercentThreshold) {
+ Write-Verbose "Failure threshold breached. There are $($Summary.PercentageFailed) percent of clients that failed this deployment"
+ Send-FailNotification -Method 'Email'
+ } elseif ($Summary.NumberTargeted -eq $Summary.NumberUnknown) {
+ Write-Verbose 'It appears the deployment has not started'
+ Send-FailNotification -Method 'Email'
+ } else {
+ Write-Verbose "Failure threshold not breached. There are only $($Summary.PercentageFailed) percent of clients that failed this deployment"
+ }
+} catch {
+ Write-Error $_.Exception.Message
+}
+}
+
+end {
+ Set-Location $Location
+}
\ No newline at end of file
diff --git a/Test-PotentialScomAgent.ps1 b/Test-PotentialScomAgent.ps1
new file mode 100644
index 0000000..8c42904
--- /dev/null
+++ b/Test-PotentialScomAgent.ps1
@@ -0,0 +1,120 @@
+function Test-Port {
+ <#
+ .SYNOPSIS
+ This function tests for open TCP/UDP ports.
+ .DESCRIPTION
+ This function tests any TCP/UDP port to see if it's open or closed.
+ .NOTES
+ Known Issue: If this function is called within 10-20 consecutively on the same port
+ and computer, the UDP port check will output $false when it can be
+ $true. I haven't figured out why it does this.
+ .PARAMETER Computername
+ One or more remote, comma-separated computer names
+ .PARAMETER Port
+ One or more comma-separated port numbers you'd like to test.
+ .PARAMETER Protocol
+ The protocol (UDP or TCP) that you'll be testing
+ .PARAMETER TcpTimeout
+ The number of milliseconds that the function will wait until declaring
+ the TCP port closed.
+ .PARAMETER
+ The number of millieconds that the function will wait until declaring
+ the UDP port closed.
+ .EXAMPLE
+ PS> Test-Port -Computername 'LABDC','LABDC2' -Protocol TCP 80,443
+
+ This example tests the TCP network ports 80 and 443 on both the LABDC
+ and LABDC2 servers.
+ #>
+ [CmdletBinding(DefaultParameterSetName = 'TCP')]
+ [OutputType([System.Management.Automation.PSCustomObject])]
+ param (
+ [Parameter(Mandatory)]
+ [string[]]$ComputerName,
+ [Parameter(Mandatory)]
+ [int[]]$Port,
+ [Parameter(Mandatory)]
+ [ValidateSet('TCP', 'UDP')]
+ [string]$Protocol,
+ [Parameter(ParameterSetName = 'TCP')]
+ [int]$TcpTimeout = 1000,
+ [Parameter(ParameterSetName = 'UDP')]
+ [int]$UdpTimeout = 1000
+ )
+ process {
+ foreach ($Computer in $ComputerName) {
+ foreach ($Portx in $Port) {
+ $Output = @{ 'Computername' = $Computer; 'Port' = $Portx; 'Protocol' = $Protocol; 'Result' = '' }
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Beginning port test on '$Computer' on port '$Protocol`:$Portx'"
+ if ($Protocol -eq 'TCP') {
+ $TcpClient = New-Object System.Net.Sockets.TcpClient
+ $Connect = $TcpClient.BeginConnect($Computer, $Portx, $null, $null)
+ $Wait = $Connect.AsyncWaitHandle.WaitOne($TcpTimeout, $false)
+ if (!$Wait) {
+ $TcpClient.Close()
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' failed port test on port '$Protocol`:$Portx'"
+ $Output.Result = $false
+ } else {
+ $TcpClient.EndConnect($Connect)
+ $TcpClient.Close()
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' passed port test on port '$Protocol`:$Portx'"
+ $Output.Result = $true
+ }
+ $TcpClient.Close()
+ $TcpClient.Dispose()
+ } elseif ($Protocol -eq 'UDP') {
+ $UdpClient = New-Object System.Net.Sockets.UdpClient
+ $UdpClient.Client.ReceiveTimeout = $UdpTimeout
+ $UdpClient.Connect($Computer, $Portx)
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Sending UDP message to computer '$Computer' on port '$Portx'"
+ $a = new-object system.text.asciiencoding
+ $byte = $a.GetBytes("$(Get-Date)")
+ [void]$UdpClient.Send($byte, $byte.length)
+ #IPEndPoint object will allow us to read datagrams sent from any source.
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Creating remote endpoint"
+ $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any, 0)
+ try {
+ #Blocks until a message returns on this socket from a remote host.
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Waiting for message return"
+ $receivebytes = $UdpClient.Receive([ref]$remoteendpoint)
+ [string]$returndata = $a.GetString($receivebytes)
+ If ($returndata) {
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' passed port test on port '$Protocol`:$Portx'"
+ $Output.Result = $true
+ }
+ } catch {
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' failed port test on port '$Protocol`:$Portx' with error '$($_.Exception.Message)'"
+ $Output.Result = $false
+ }
+ $UdpClient.Close()
+ $UdpClient.Dispose()
+ }
+ [pscustomobject]$Output
+ }
+ }
+ }
+}
+
+$Computername = ''
+$RequiredSerices = 'Netlogon', 'RemoteRegistry'
+$RequiredPorts = @(
+ @{ 'Protocol' = 'TCP'; 'Port' = 5723 },
+ @{ 'Protocol' = 'UDP'; 'Port' = 5723 },
+ @{ 'Protocol' = 'TCP'; 'Port' = 135 },
+ @{ 'Protocol' = 'UDP'; 'Port' = 135 },
+ @{ 'Protocol' = 'TCP'; 'Port' = 137 },
+ @{ 'Protocol' = 'UDP'; 'Port' = 137 },
+ @{ 'Protocol' = 'TCP'; 'Port' = 139 },
+ @{ 'Protocol' = 'UDP'; 'Port' = 139 },
+ @{ 'Protocol' = 'TCP'; 'Port' = 445 }
+)
+
+foreach ($Port in $RequiredPorts) {
+ Test-Port -ComputerName $Computername -Protocol $Port.Protocol -Port $Port.Port
+}
+
+## DNS hostname works forward and back
+
+## TCP/UDP port 5723,135,137,139 is open and the remote administration (RPC) rule is added, TCP/445 is open
+
+## the SCOM action account is a local admin somehow
\ No newline at end of file
diff --git a/Test-ServerRolePortGroup.ps1 b/Test-ServerRolePortGroup.ps1
new file mode 100644
index 0000000..d4be7cb
--- /dev/null
+++ b/Test-ServerRolePortGroup.ps1
@@ -0,0 +1,167 @@
+<#
+.SYNOPSIS
+ This function tests for open TCP/UDP ports by server role.
+.DESCRIPTION
+ This function tests for all the approprite TCP/UDP ports by server role so you don't have
+ to memorize or look up all of the ports that need to be tested for every time
+ you want to verify remote connectivity on a specific server role.
+.NOTES
+ Link port references:
+ http://technet.microsoft.com/en-us/library/dd772723(v=ws.10).aspx
+ http://en.wikipedia.org/wiki/Server_Message_Block
+ http://technet.microsoft.com/en-us/library/cc940063.aspx
+.PARAMETER Computername
+ One or more remote, comma-separated computer names
+.PARAMETER ServerRole
+ The services on the computer that you'd like to find open ports for. This can be
+ common services like WinRm, Smb, Dns, Active Directory and NetBIOS
+.EXAMPLE
+ PS> Test-ServerRolePortGroup -Computername 'LABDC','LABDC2' -ServerRole NetBIOS,WinRm,Dns
+
+ This example tests the network ports necessary for NetBIOS, WinRm and Dns
+ to operate on the servers LABDC and LABDC2.
+#>
+
+[CmdletBinding()]
+[OutputType([System.Management.Automation.PSCustomObject])]
+param (
+ [Parameter(Mandatory)]
+ [ValidateScript({ Test-Connection -ComputerName $_ -Count 1 -Quiet})]
+ [string[]]$Computername,
+ [Parameter(Mandatory)]
+ [ValidateSet('WinRm','Smb','Dns','ActiveDirectoryGeneral','ActiveDirectoryGlobalCatalog','NetBios')]
+ [string[]]$ServerRole
+)
+begin {
+
+ function Test-Port {
+ <#
+ .SYNOPSIS
+ This function tests for open TCP/UDP ports.
+ .DESCRIPTION
+ This function tests any TCP/UDP port to see if it's open or closed.
+ .NOTES
+ Known Issue: If this function is called within 10-20 consecutively on the same port
+ and computer, the UDP port check will output $false when it can be
+ $true. I haven't figured out why it does this.
+ .PARAMETER Computername
+ One or more remote, comma-separated computer names
+ .PARAMETER Port
+ One or more comma-separated port numbers you'd like to test.
+ .PARAMETER Protocol
+ The protocol (UDP or TCP) that you'll be testing
+ .PARAMETER TcpTimeout
+ The number of milliseconds that the function will wait until declaring
+ the TCP port closed.
+ .PARAMETER
+ The number of millieconds that the function will wait until declaring
+ the UDP port closed.
+ .EXAMPLE
+ PS> Test-Port -Computername 'LABDC','LABDC2' -Protocol TCP 80,443
+
+ This example tests the TCP network ports 80 and 443 on both the LABDC
+ and LABDC2 servers.
+ #>
+ [CmdletBinding(DefaultParameterSetName = 'TCP')]
+ [OutputType([System.Management.Automation.PSCustomObject])]
+ param (
+ [Parameter(Mandatory)]
+ [string[]]$ComputerName,
+ [Parameter(Mandatory)]
+ [int[]]$Port,
+ [Parameter(Mandatory)]
+ [ValidateSet('TCP', 'UDP')]
+ [string]$Protocol,
+ [Parameter(ParameterSetName = 'TCP')]
+ [int]$TcpTimeout = 1000,
+ [Parameter(ParameterSetName = 'UDP')]
+ [int]$UdpTimeout = 1000
+ )
+ process {
+ foreach ($Computer in $ComputerName) {
+ foreach ($Portx in $Port) {
+ $Output = @{ 'Computername' = $Computer; 'Port' = $Portx; 'Protocol' = $Protocol; 'Result' = '' }
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Beginning port test on '$Computer' on port '$Protocol`:$Portx'"
+ if ($Protocol -eq 'TCP') {
+ $TcpClient = New-Object System.Net.Sockets.TcpClient
+ $Connect = $TcpClient.BeginConnect($Computer, $Portx, $null, $null)
+ $Wait = $Connect.AsyncWaitHandle.WaitOne($TcpTimeout, $false)
+ if (!$Wait) {
+ $TcpClient.Close()
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' failed port test on port '$Protocol`:$Portx'"
+ $Output.Result = $false
+ } else {
+ $TcpClient.EndConnect($Connect)
+ $TcpClient.Close()
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' passed port test on port '$Protocol`:$Portx'"
+ $Output.Result = $true
+ }
+ $TcpClient.Close()
+ $TcpClient.Dispose()
+ } elseif ($Protocol -eq 'UDP') {
+ $UdpClient = New-Object System.Net.Sockets.UdpClient
+ $UdpClient.Client.ReceiveTimeout = $UdpTimeout
+ $UdpClient.Connect($Computer, $Portx)
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Sending UDP message to computer '$Computer' on port '$Portx'"
+ $a = new-object system.text.asciiencoding
+ $byte = $a.GetBytes("$(Get-Date)")
+ [void]$UdpClient.Send($byte, $byte.length)
+ #IPEndPoint object will allow us to read datagrams sent from any source.
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Creating remote endpoint"
+ $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any, 0)
+ try {
+ #Blocks until a message returns on this socket from a remote host.
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Waiting for message return"
+ $receivebytes = $UdpClient.Receive([ref]$remoteendpoint)
+ [string]$returndata = $a.GetString($receivebytes)
+ If ($returndata) {
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' passed port test on port '$Protocol`:$Portx'"
+ $Output.Result = $true
+ }
+ } catch {
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - '$Computer' failed port test on port '$Protocol`:$Portx' with error '$($_.Exception.Message)'"
+ $Output.Result = $false
+ }
+ $UdpClient.Close()
+ $UdpClient.Dispose()
+ }
+ [pscustomobject]$Output
+ }
+ }
+ }
+ }
+
+ $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
+
+ $PortGroups = @{
+ 'WinRm' = @{ 'TCP' = 5985}
+ 'Smb' = @{ 'TCP' = 445; 'UDP' = 445 }
+ 'Dns' = @{ 'TCP' = 53; 'UDP' = 53 }
+ 'ActiveDirectoryGeneral' = @{ 'TCP' = 25, 88, 389, 464, 636, 5722, 9389; 'UDP' = 88,123,389,464 }
+ 'ActiveDirectoryGlobalCatalog' = @{ 'TCP' = 3268, 3269 }
+ 'NetBios' = @{ 'TCP' = 135, 137, 138, 139; 'UDP' = 137,138,139 }
+ }
+}
+process {
+ foreach ($Computer in $Computername) {
+ Write-Verbose "Beginning port tests on computer '$Computer'"
+ try {
+ $TestPortGroups = $PortGroups.GetEnumerator() | where { $ServerRole -contains $_.Key }
+ Write-Verbose "Found '$($TestPortGroups.Count)' port group(s) to test"
+ foreach ($PortGroup in $TestPortGroups) {
+ $PortGroupName = $PortGroup.Key
+ $PortGroupValues = $PortGroup.Value
+ foreach ($Value in $PortGroupValues.GetEnumerator()) {
+ $Protocol = $Value.Key
+ $Ports = $Value.Value
+ $TestResult = Test-Port -ComputerName $Computer -Protocol $Protocol -Port $Ports
+ $TestResult | Add-Member -MemberType 'NoteProperty' -Name 'PortSet' -Value $PortGroupName
+ $TestResult
+ }
+ }
+ } catch {
+ Write-Verbose "$($MyInvocation.MyCommand.Name) - Computer: $Computer - Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
+ $false
+ }
+ }
+}
\ No newline at end of file
diff --git a/TimeframeInvestigator.ps1 b/TimeframeInvestigator.ps1
new file mode 100644
index 0000000..dc4060d
--- /dev/null
+++ b/TimeframeInvestigator.ps1
@@ -0,0 +1,142 @@
+#requires -version 3
+
+<#
+.SYNOPSIS
+ This script searches a Windows computer for all event log or text log entries
+ between a specified start and end time.
+.DESCRIPTION
+ This script enumerates all event logs and all text logs on a local or remote
+ Windows computer. It looks for any entries between a specified start and end
+ time. It then copies this activity to an output location for further analysis.
+.EXAMPLE
+ SCRIPTNAME -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName COMPUTERNAME -OutputDirectory $desktop\trouble_logs -SkipEventLog Security
+.PARAMETER computername
+ The computer name to query. Just one.
+.PARAMETER logname
+ The name of a file to write failed computer names to. Defaults to errors.txt.
+#>
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory)]
+ [datetime]$StartTimestamp,
+ [Parameter(Mandatory)]
+ [datetime]$EndTimestamp,
+ [Parameter(ValueFromPipeline,
+ ValueFromPipelineByPropertyName)]
+ [string]$ComputerName = 'localhost',
+ [Parameter()]
+ [string]$OutputDirectory = ".\$Computername",
+ [Parameter()]
+ [string]$LogAuditFilePath = "$OutputDirectory\LogActivity.csv",
+ [Parameter()]
+ [switch]$EventLogsOnly,
+ [Parameter()]
+ [switch]$LogFilesOnly,
+ [Parameter()]
+ [string[]]$ExcludeDirectory,
+ [Parameter()]
+ [string[]]$FileExtension = @('log', 'txt', 'wer')
+)
+
+begin {
+ $LogsFolderPath = "$OutputDirectory\logs"
+ if (!(Test-Path $LogsFolderPath)) {
+ mkdir $LogsFolderPath | Out-Null
+ }
+
+ function Add-ToLog($FilePath,$LineText,$LineNumber,$MatchType) {
+ $Audit = @{
+ 'FilePath' = $FilePath;
+ 'LineText' = $LineText
+ 'LineNumber' = $LineNumber
+ 'MatchType' = $MatchType
+ }
+ [pscustomobject]$Audit | Export-Csv -Path $LogAuditFilePath -Append -NoTypeInformation
+ }
+}
+
+process {
+
+ if (!($LogFilesOnly.IsPresent)) {
+ $Logs = (Get-WinEvent -ListLog * -ComputerName $ComputerName | where { $_.RecordCount }).LogName
+ $FilterTable = @{
+ 'StartTime' = $StartTimestamp
+ 'EndTime' = $EndTimestamp
+ 'LogName' = $Logs
+ }
+
+ $Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $FilterTable -ea 'SilentlyContinue'
+ Write-Verbose "Found $($Events.Count) total events"
+
+ ## Convert the properties to something friendlier
+ $LogProps = @{ }
+ [System.Collections.ArrayList]$MyEvents = @()
+ foreach ($Event in $Events) {
+ $LogProps.Time = $Event.TimeCreated
+ $LogProps.Source = $Event.ProviderName
+ $LogProps.EventId = $Event.Id
+ $LogProps.Message = $Event.Message.Replace("`n", '|').Replace("`r", '|')
+ $LogProps.EventLog = $Event.LogName
+ $MyEvents.Add([pscustomobject]$LogProps) | Out-Null
+ }
+ $MyEvents | sort Time | Export-Csv -Path "$OutputDirectory\eventlogs.txt" -Append -NoTypeInformation
+ }
+
+ if (!($EventLogsOnly.IsPresent)) {
+ ## Enumerate all shares
+ $Shares = Get-WmiObject -ComputerName $ComputerName -Class Win32_Share | where { $_.Path -match '^\w{1}:\\$' }
+ [System.Collections.ArrayList]$AccessibleShares = @()
+ foreach ($Share in $Shares) {
+ $Share = "\\$ComputerName\$($Share.Name)"
+ if (!(Test-Path $Share)) {
+ Write-Warning "Unable to access the '$Share' share on '$Computername'"
+ } else {
+ $AccessibleShares.Add($Share) | Out-Null
+ }
+ }
+
+ $AllFilesQueryParams = @{
+ Path = $AccessibleShares
+ Recurse = $true
+ Force = $true
+ ErrorAction = 'SilentlyContinue'
+ File = $true
+ }
+ if ($ExcludeDirectory) {
+ $AllFilesQueryParams.ExcludeDirectory = $ExcludeDirectory
+ }
+ ##TODO: Add capability to match on Jan,Feb,Mar,etc
+ $DateTimeRegex = "($($StartTimestamp.Month)[\\.\-/]?$($StartTimestamp.Day)[\\.\-/]?[\\.\-/]$($StartTimestamp.Year))|($($StartTimestamp.Year)[\\.\-/]?$($StartTimestamp.Month)[\\.\-/]?[\\.\-/]?$($StartTimestamp.Day))"
+ Get-ChildItem @AllFilesQueryParams | where { $_.Length -ne 0 } | foreach {
+ try {
+ Write-Verbose "Processing file '$($_.Name)'"
+ if (($_.LastWriteTime -ge $StartTimestamp) -and ($_.LastWriteTime -le $EndTimestamp)) {
+ Write-Verbose "Last write time within timeframe for file '$($_.Name)'"
+ Add-ToLog -FilePath $_.FullName -MatchType 'LastWriteTime'
+ }
+ if ($FileExtension -contains $_.Extension.Replace('.','') -and !((Get-Content $_.FullName -Encoding Byte -TotalCount 1024) -contains 0)) {
+ ## Check the contents of text file to references to dates in the timeframe
+ Write-Verbose "Checking log file '$($_.Name)' for date/time match in contents"
+ $LineMatches = Select-String -Path $_.FullName -Pattern $DateTimeRegex
+ if ($LineMatches) {
+ Write-Verbose "Date/time match found in file '$($_.FullName)'"
+ foreach ($Match in $LineMatches) {
+ Add-ToLog -FilePath $_.FullName -LineNumber $Match.LineNumber -LineText $Match.Line -MatchType 'Contents'
+ }
+ ## I must create the directory ahead of time if it doesn't exist because
+ ## Copy-Item doesn't have the ability to automatically create the directory
+ $Trim = $_.FullName.Replace("\\$Computername\", '')
+ $Destination = "$OutputDirectory\$Trim"
+ if (!(Test-Path $Destination)) {
+ ##TODO: Remove the error action when long path support is implemented
+ mkdir $Destination -ErrorAction SilentlyContinue | Out-Null
+ }
+ Copy-Item -Path $_.FullName -Destination $Destination -ErrorAction SilentlyContinue -Recurse
+ }
+ }
+ } catch {
+ Write-Warning $_.Exception.Message
+ }
+ }
+ }
+}