Modifying SSL cert check Powershell script to loop through multiple sites - powershell

I'm a Powershell newb, but I am trying to write a script to check the SSL certificate expiration dates for multiple remote websites.
I found this script (http://www.zerrouki.com/checkssl/) that does what I want, but only for a single site.
I am trying to modify it to allow for multiple sites/checks, but am getting an error when I do so. I've removed all of the email functionality from the script as I'll be using another to tool to alert on expiring certs. And I've hardcoded the URLs to check.
<#
Modified from Fabrice ZERROUKI - fabricezerrouki#hotmail.com Check-SSL.ps1
#>
$WebsiteURLs= #("URL1.com","URL2.com","URL3.com")
$WebsitePort=443
$CommonName=$WebsiteURL
$Threshold=120
foreach ($WebsiteURL in $WebsiteURLs){
Try{
$Conn = New-Object System.Net.Sockets.TcpClient($WebsiteURL,$WebsitePort)
Try {
$Stream = New-Object System.Net.Security.SslStream($Conn.GetStream())
$Stream.AuthenticateAsClient($CommonName)
$Cert = $Stream.Get_RemoteCertificate()
$ValidTo = [datetime]::Parse($Cert.GetExpirationDatestring())
Write-Host "`nConnection Successfull" -ForegroundColor DarkGreen
Write-Host "Website: $WebsiteURL"
$ValidDays = $($ValidTo - [datetime]::Now).Days
if ($ValidDays -lt $Threshold)
{
Write-Host "`nStatus: Warning (Expires in $ValidDays days)" -ForegroundColor Yellow
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor Yellow
}
else
{
Write-Host "`nStatus: OK" -ForegroundColor DarkGreen
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor DarkGreen
}
}
Catch { Throw $_ }
Finally { $Conn.close() }
}
Catch {
Write-Host "`nError occurred connecting to $($WebsiteURL)" -ForegroundColor Yellow
Write-Host "Website: $WebsiteURL"
Write-Host "Status:" $_.exception.innerexception.message -ForegroundColor Yellow
Write-Host ""
}
}
When I run this (with valid sites in the $WebsiteURLs variable) every site returns: Status: Authentication failed because the remote party has closed the transport stream.
If I only put one site in the $WebsiteURLs variable and remove the foreach function it runs ok.
Any idea what I can do to make this loop through each site in the variable?

Problem lies here:
$WebsiteURLs= #("URL1.com","URL2.com","URL3.com")
$WebsitePort=443
$CommonName=$WebsiteURL
When you call $Stream.AuthenticateAsClient($CommonName) it doesn't work, because $CommonName=$WebsiteURL is setting $commonName to null. When you remove the loop I assume you did as I did and changed $WebsiteURLs to $WebsiteURL so then you had a value to assign $CommonName.
If you move the declaration of $CommonName to within your loop it works.
$WebsiteURLs= #("URL1.com","URL2.com","URL3.com")
$WebsitePort=443
$Threshold=120
foreach ($WebsiteURL in $WebsiteURLs){
$CommonName=$WebsiteURL
Try{
$Conn = New-Object System.Net.Sockets.TcpClient($WebsiteURL,$WebsitePort)
Try {
$Stream = New-Object System.Net.Security.SslStream($Conn.GetStream())
$Stream.AuthenticateAsClient($CommonName)
$Cert = $Stream.Get_RemoteCertificate()
$ValidTo = [datetime]::Parse($Cert.GetExpirationDatestring())
Write-Host "`nConnection Successfull" -ForegroundColor DarkGreen
Write-Host "Website: $WebsiteURL"
$ValidDays = $($ValidTo - [datetime]::Now).Days
if ($ValidDays -lt $Threshold)
{
Write-Host "`nStatus: Warning (Expires in $ValidDays days)" -ForegroundColor Yellow
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor Yellow
}
else
{
Write-Host "`nStatus: OK" -ForegroundColor DarkGreen
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor DarkGreen
}
}
Catch { Throw $_ }
Finally { $Conn.close() }
}
Catch {
Write-Host "`nError occurred connecting to $($WebsiteURL)" -ForegroundColor Yellow
Write-Host "Website: $WebsiteURL"
Write-Host "Status:" $_.exception.innerexception.message -ForegroundColor Yellow
Write-Host ""
}
}

Related

Coloring output

I made a very very simple code that just checks a few regkeys. However to make it more nice to the eyes I was hoping that whenever it's False i can make it red and whenever it's True i can make it green.
I googled a bunch about this but couldn't find a clear solution for what i'm trying to accomplish. Any tips are very much appreciated.
Write-Host "Update Pending" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending'
""
Write-Host "Reboot Pending:" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
""
Write-Host "Reboot Required:" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
""
Write-Host "Pending File Rename Operations:" -ForegroundColor Cyan
Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations'
""
Write-Host "Beschikbare Updates:" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Updates\UpdateExeVolatile'
Save the output from Test-Path to a variable, the use the value to specify one color or the other to pass as an argument to Write-Host:
Write-Host "Update Pending" -ForegroundColor Cyan
$testResult = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending'
Write-Host $testResult -ForegroundColor #('Red', 'Green')[$testResult]
Write-Host ''
PowerShell will implicitly convert $False to 0 and $True to 1 when used in an array indexer, so $False results in 'Red' being picked.
Since you're basically repeating the same test operation every time, you can simplify your code by organizing the labels and registry keys into a hashtable and then only write the code to test and output once:
$testCases = [ordered]#{
"Update Pending" = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending'
"Reboot Pending" = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
"Reboot Required" = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
"Pending File Rename Operations" = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations'
"Beschikbare Updates" = 'HKLM:\SOFTWARE\Microsoft\Updates\UpdateExeVolatile'
}
foreach($label in $testCases.psbase.Keys){
Write-Host "${label}:" -ForegroundColor Cyan
$testResult = Test-Path $testCases[$label]
Write-Host $testResult -ForegroundColor #('Red','Green')[$testResult]
Write-Host ''
}

Comparing local file (Get-Content) with web file (Invoke-WebRequest) always results in not equal

Basically I'm writing an updater for my script. It's supposed to compare itself (Get-Content -Path $PSCommandPath) to the one on the web server (Invoke-WebRequest...). However, it always shows an updated is available.
My understanding (from other questions here) is that -raw will force it to return the file as a string which should be the same way the web request is returned. But I'm guessing the issue is somewhere in there. Hopefully a simple thing...
function checkUpdates() {
Write-Host "Checking for updates... " -NoNewline -ForegroundColor $clrMain
$item1 = Get-Content -Path $PSCommandPath -raw
$item2 = Invoke-WebRequest -Uri "$($config.remote)/launcher/launch.ps1"
if ($item1 -eq $item2) {
Write-Host "Up to date" -ForegroundColor $clrSuccess
} else {
Write-Host "New version available" -ForegroundColor $clrWarn
invoke-expression "./update.ps1 $($config.remote)"
exit
}
}
I was able to replace invoke-webrequest with curl and replace the comparison to accomplish what I wanted.
function checkUpdates() {
Write-Host "Checking for updates... " -NoNewline -ForegroundColor $clrMain
$item1 = Get-Content -Path $PSCommandPath
$item2 = curl "$($config.remote)/launcher/launch.ps1"
if (!(Compare-Object -ReferenceObject $item1 -DifferenceObject $item2)) {
Write-Host "Up to date" -ForegroundColor $clrSuccess
} else {
Write-Host "New version available" -ForegroundColor $clrWarn
}
}

How to handle Active Directory exceptions via powershell?

I am trying to handle an ActiveDirectoryObjectNotFoundException exception in PowerShell when using the Forest.GetForest method.
https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory.forest.getforest(v=vs.110).aspx
# Clear screen
Clear
# Change below as per your requirements
$context='forest'
$name='My.Lab.Local'
$username="fake\Administrator"
$password="FakePassword"
Write-Host -Object "Connecting $context... -> $name " -BackgroundColor Yellow -ForegroundColor Blue
try
{
$DC = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($context,$name,$username,$password)
Write-Host -Object "Successfully connected to $context using discovery account $username." -BackgroundColor Green -ForegroundColor Blue
Write-Host -Object "Retrieving details of the forest..." -BackgroundColor Yellow -ForegroundColor Blue
$Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($DC)
$Forest.Name
Write-Host -Object "Successfully retrived your forest..." -BackgroundColor Green -ForegroundColor Blue
}
catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException]
{
Write-Host "ActiveDirectoryObjectNotFoundException exception"
}
catch [System.Security.Authentication.AuthenticationException]
{
Write-Host "AuthenticationException exception ( Catch Block )"
}
finally
{
Write-Host "cleaning up ...( Finally Block )"
}
The Output
Connecting forest... -> Web.Metacash.Com
Successfully connected to forest using discovery account fake\Administrator.
Retrieving details of the forest...
AuthenticationException exception ( Catch Block )
cleaning up ...( Finally Block )
How to do I get the original failing message instead of giving my own message, like using $_.message or something?
$_ should have an ErrorRecord in the catch block. The exception should be in there. For example, use $_.Exception.Message to get its message. Of course the error record has more info about the error. $_.InvocationInfo.ScriptLineNumber would have the line number where the error occurred.

Pause on failure, retry input from CSV

I have 2 sections of PowerShell code that exit the script on failure. After thinking about it I realized it would make more sense to pause on a failure and allow the admin time to remediate... then when the any key is paused, retry the same line in the CSV. Here's how it looks now:
#check tools status first
Write-Host ""
Write-Host "Checking VMware Tools Status before proceeding." -foreground green
Write-Host ""
foreach ($item in $vmlist) {
$vmname = $item.vmname
$ToolsStatus = (Get-VM $vmname).extensiondata.Guest.ToolsStatus -eq "toolsNotRunning"
if ($ToolsStatus -eq $true) {
Write-Host ""
Write-Host "Tools is not installed or running on $vmname. Remediate on guest and restart the script" -foreground Yellow
Write-Host "Script will continue to exit until tools is running on $vmname" -foreground yellow
Write-Host ""
exit
} else {
Write-Host ""
Write-Host "Tools running on all VMs, script will continue" -foreground green
Write-Host ""
}
}
I know how to put the pause in $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown'), but I have no idea to do the loop so it retries.
I'm using similar code elsewhere to validate that VMs are powered up so this will work in both sections.
EDIT: Here's the re-worked script. Would this work?
foreach ($item in $vmlist) {
$vmname = $item.vmname
do {
$ToolsStatus = (Get-VM $vmname).extensiondata.Guest.ToolsStatus -eq "toolsNotRunning"
if ($ToolsStatus) {
Write-Host ""
Write-Host "Tools is not installed or running on $vmname." -Foreground yellow
Write-Host "Remediate VMware tools on $vmname and"
Write-host "Press any key to retry..."
$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
} until ($ToolsStatus -eq "PoweredOn")
Write-Host "Tools running on $vmname, script will continue" -Foreground green
}
Add a nested do..while loop like this:
foreach ($item in $vmlist) {
$vmname = $item.vmname
do {
$ToolsMissing = (Get-VM $vmname).extensiondata.Guest.ToolsStatus -eq "toolsNotRunning"
if ($ToolsMissing) {
Write-Host "Tools is not installed or ..." -Foreground yellow
$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
} while ($ToolsMissing)
}
Write-Host "Tools running on all VMs, script will continue" -Foreground green

How do I set a SharePoint-hosted app's permissions via PowerShell?

I'm deploying apps using Import-SPAppPackage and Install-SPApp. I'd like to be able to use Set-AppPrincipalPermission to set permissions but I can't get it working.
I'm uploading a SharePoint-hosted app to SharePoint using the PowerShell cmdlets Import-SPAppPackage and Install-SPApp. This is working fine for SharePoint-hosted apps that do not require additional permissions.
However, one app needs read access to the site, so this is declared in the manifest. And it works fine when run through Visual Studio - on first launch, it correctly asks to trust the app for read access to the site.
When I add this app via PowerShell, it has no opportunity to ask. The install continues without problems, but then the app doesn't work. (It fails with a permissions problem, which is absolutely the correct behavour since the permissions haven't yet been granted.)
I can fix the permissions by going to the Site Contents, clicking on the '...' for the problem app, choosing 'Permissions' and then clicking the link that says 'If there's something wrong with the app's permissions, click here to trust it again'.
But I really want to just be able to do the whole deployment via PowerShell.
The Set-AppPrincipalPermission cmdlet should allow me to set the permissions, but I can't get it to work. Specifically, I can't get a handle on the app principal that was automatically created when the app was deployed, so I can't pass this app principal to Set-AppPrincipalPermission.
The app principal has a name of the form 'i:0i.t|ms.sp.int|#' and it is listed on /_layouts/15/appprincipals.aspx. When I use Get-SPAppPrincipal with it, all I get is:
Get-SPAppPrincipal : The app principal could not be found.
I haven't seen any examples of using Get-SPAppPrincipal for any SharePoint-hosted apps - they all seem to be for provider-hosted apps. They also all seem to just use an app principal ID built from the client ID and the realm ID, but my SharePoint-hosted app doesn't have a client ID.
Is it possible to get the app principal of a SharePoint-hosted app and use it to set the permissions via PowerShell? Am I doing something wrong, or is there another approach?
I struggled the same problem like you and finally found an answer in these two blogs:
Blog with a nice Install, Update and Delete Script
Here is a nice post about "pressing" the "Trust It" Button via PowerShell Link
And because I know how lazy programmers like me are, feel free to use this merged script to Install Apps:
param
(
[string]$Web = $(throw '- Need a SharePoint web site URL (e.g. "http://portal.contoso.com/")'),
[string]$Source = "ObjectModel"
)
Write-Host -ForegroundColor White "-------------------"
Write-Host -ForegroundColor White "| App Installer |"
Write-Host -ForegroundColor White "-------------------"
Write-Host -ForegroundColor White "- "
#Global vars
$AppPackageName = "App.app";
#Loads powershell settings
Write-Host -ForegroundColor White "- Load Powershell context.."
$0 = $myInvocation.MyCommand.Definition
$dp0 = [System.IO.Path]::GetDirectoryName($0)
#Loads the SharePoint snapin
Write-Host -ForegroundColor White "- Load SharePoint context.."
$ver = $host | select version
if ($ver.Version.Major -gt 1) {$host.Runspace.ThreadOptions = "ReuseThread"}
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
Add-PSSnapin "Microsoft.SharePoint.PowerShell";
}
[void][System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
#Imports the App package
Write-Host -ForegroundColor White "- Import app package '$AppPackageName'..."
$appPath = "C:\Projects\App\App\bin\Debug\app.publish\1.0.0.0" + "\" + $AppPackageName;
if ($Source.Equals("ObjectModel", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::ObjectModel);
}
elseif ($Source.Equals("Marketplace", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::Marketplace);
}
elseif ($Source.Equals("CorporateCatalog", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::CorporateCatalog);
}
elseif ($Source.Equals("DeveloperSite", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::DeveloperSite);
}
elseif ($Source.Equals("RemoteObjectModel", [System.StringComparison]::InvariantCultureIgnoreCase)) {
$sourceApp = ([microsoft.sharepoint.administration.spappsource]::RemoteObjectModel);
}
$spapp = Import-SPAppPackage -Path "$appPath" -Site $Web -Source $sourceApp -Confirm:$false -ErrorAction SilentlyContinue -ErrorVariable err;
if ($err -or ($spapp -eq $null))
{
Write-Host -ForegroundColor Yellow "- An error occured during app import !"
throw $err;
}
Write-Host -ForegroundColor White "- Package imported with success."
#Installs the App
Write-Host -ForegroundColor White "- Install the APP in web site..."
$app = Install-SPApp -Web $Web -Identity $spapp -Confirm:$false -ErrorAction SilentlyContinue -ErrorVariable err;
if ($err -or ($app -eq $null)) {
Write-Host -ForegroundColor Yellow "- An error occured during app installation !"
throw $err;
}
$AppName = $app.Title;
Write-Host -ForegroundColor White "- App '$AppName' registered, please wait during installation..."
$appInstance = Get-SPAppInstance -Web $Web | where-object {$_.Title -eq $AppName};
$counter = 1;
$maximum = 150;
$sleeptime = 2;
Write-Host -ForegroundColor White "- Please wait..." -NoNewline;
$url = "$($Web)_layouts/15/appinv.aspx?AppInstanceId={$($appInstance.Id)}"
$ie = New-Object -com internetexplorer.application
try
{
$ie.visible=$true
$ie.navigate2($url)
while ($ie.busy)
{
sleep -milliseconds 60
}
$trustButton = $ie.Document.getElementById("ctl00_PlaceHolderMain_BtnAllow")
$trustButton.click()
sleep -Seconds 1
Write-Host "App was trusted successfully!"
}
catch
{
throw ("Error Trusting App");
}
while (($appInstance.Status -eq ([Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Installing)) -and ($counter -lt $maximum))
{
Write-Host -ForegroundColor White "." -NoNewline;
sleep $sleeptime;
$counter++;
$appInstance = Get-SPAppInstance -Web $Web | where-object {$_.Title -eq $AppName}
}
Write-Host -ForegroundColor White ".";
if ($appInstance.Status -eq [Microsoft.SharePoint.Administration.SPAppInstanceStatus]::Installed) {
Write-Host -ForegroundColor White "- The App was successfully installed.";
$appUrl = $appInstance.AppWebFullUrl;
Write-Host -ForegroundColor White "- The App is now available at '$appUrl'.";
Write-Host -ForegroundColor White "- (Don't forget to add app host name in your host file if necessary...).";
Write-Host -ForegroundColor White "- "
}
else {
Write-Host -ForegroundColor Yellow "- An unknown error has occured during app installation. Read SharePoint log for more information.";
}
Figured out a way other than using IE.
Basically just using powershell to call SPAppPrincipalPermissionsManager.AddAppPrincipalToWeb
$rootUrl = "https://ur-sp.com"
$urlSiteName = "ur-site"
$web = Get-SPWeb "$rootUrl/$urlSiteName"
$appPrincipalManager = [Microsoft.SharePoint.SPAppPrincipalManager]::GetManager($web)
$applicationEndPointAuthorities = new-object System.Collections.Generic.List[string]
$applicationEndPointAuthorities.Add("$rootUrl/$urlSiteName");
$symmetricKey = New-Object System.Security.SecureString;
$datetimeNow = [System.DateTime]::Now
$credential = [Microsoft.SharePoint.SPAppPrincipalCredential]::CreateFromSymmetricKey($symmetricKey,$datetimeNow,$datetimeNow)
$creationParameters =New-Object Microsoft.SharePoint.SPExternalAppPrincipalCreationParameters($appid,$appFriendlyName,$applicationEndPointAuthorities,$credential)
$appPrincipal = $appPrincipalManager.CreateAppPrincipal($creationParameters)
$appPrincipalPermissionsManager = New-Object -TypeName
Microsoft.SharePoint.SPAppPrincipalPermissionsManager -ArgumentList $web
$r = $appPrincipalPermissionsManager.AddAppPrincipalToWeb($appPrincipal, 3)
3 is of SPAppPrincipalPermissionKind enum, and I don't think its value really matters.
This will do the full trust part via powershell:
$targetWeb = Get-SPSite "http://dev.my.com"
$clientID = "82ea34fc-31ba-4e93-b89a-aa41b023fa7e"
$authRealm = Get-SPAuthenticationRealm -ServiceContext $targetWeb
$AppIdentifier = $clientID + "#" + $authRealm
$appPrincipal = Get-SPAppPrincipal -Site $targetWeb.RootWeb -NameIdentifier $AppIdentifier
Set-SPAppPrincipalPermission -Site $targetWeb.RootWeb -AppPrincipal $appPrincipal -Scope SiteCollection -Right FullControl
More info here:
http://lixuan0125.wordpress.com/2013/11/18/register-and-install-app-through-powershell/