Category Archives: PowerShell

Shell of an Idea 讀後心得

A Shell of a Problem 小節

在單機的世界,OS 的重點在簡化操作,讓大多數的人都可以用,特別是非 IT 人員(這是 MS 起家的訴求)。這是 GUI 的優點。換句話說,當僅維護單台機器,少數使用者時,適合不用背指令,直覺化操作避免錯誤的 GUI。

但網路連線後,多點,多類工作,多流程,多資料結構…無法用 GUI 串接,因為要自由組合異質資料結構,透過 UI 難以呈現。這反變成 GUI 介面的弱點,IT 管理者的痛。

在多台機器維護軟/硬體,為新使用者在多台機器設定不同服務的不同權限,在多機器做集中化安裝、部署、管理、監控、批次、稽核紀錄…等,需要自動化的 Script Shell,所以 PowerShell 是訴求 MIS 自動化。

以 API 來隔絕系統是好的,其實 Text based 作業系統(Unix/Linux)等於是以文件當 API,但這缺乏有效的資料結構,變成一切都序列化/反序列化,前一個指令將結果序列化成字串,下一個指令再反序列化成資料結構,在記憶體中處理完再交給下一個,形成 Pipe 串鍊,這對開發而言有利有弊。

如同整合資料系統時,傳遞資料是否要落地,落地等於是文件化交換,這也是優劣參半。而以 API 為組合單位的系統自動化,需要包裝這些 API 的 Shell,也就是物件導向式的 PowerShell。

Culture 小節(討論開發團隊的文化)

若每一件事都可以單元組合,也就是每個單元都具備自己的生命周期,從開發到遞交,也就會要 Unit Test。當組合越複雜時,測試涵蓋率越難提升,也就越需要 Unit Test。

但這是否跟規模有關,某個規模之下,交互作用沒有複雜到難以處理。雖然交互作用是相乘的,但個數不多,基數不大,也就不會難以處理,則專門的測試者執行測試是否比較有效率?

因為把測試的工作拉到開發者,也是開法者自己內部的單元複雜度提升,每個開發者都是一個單元,分析、設計、開發、測試、部署…等,也都是一個人內部的交錯複雜度。把複雜度放在哪,如同卸載負載,對整個團隊而言,應該都是動態的移動。換句話說,把小系統的測試工作分給每個開法者做,是否是小的系統要規劃 Load Balance,恐怕是殺雞牛刀。

當然,養成寫 Unit Test 的習慣又是另一個議題,與分工的關係不大了。

The .NET Framework Connection 小節

元件化(Component-based architecture)是軟體高唱學習硬體的主旋律,但硬體受限於搬動與組合原子,現今的 Services、Serverless function 似乎更標誌著資訊本身是電子的特徵 :)。但畢竟我們是原子本體,電子傳遞,虛/實混合,線上/下整合的時代,我們的 Framework 總是混著結構化/物件導向化/元件化/服務化。

Windows 小節

天高皇帝遠的另一種解釋,皇帝挺你也沒用 ^^

讓 SSIS 可以執行 PowerShell 指令

image

要開啟 Policy:

gpedit.msc

image

image

取得 Power BI 後的 Model

感謝微軟 Egg 告知,Power BI Desktop 是在背後起一個 AS 服務,以如下的 Power BI Desktop 為例:

image

透過 PowerShell 查該 msmdsrv 的 Processid 後,透過 netstat –o 查聽的 port

image

而後透過 SSMS 連結 AS

imageP

就可以透過物件總管觀察該 Tabular Model

image

透過 SSDT 從 Server 讀回該 Model 定義會有如下的錯誤

image

可以產生 TMSL Script

image

而後再到 AS Tabular 貼上語法執行時,因為會多出 .tables[1].isPrivate、 model.tables[2].columns[3].variations…等屬性而無法部署到獨立的 AS 上,得到的錯誤訊息如下:

正在執行查詢 …
JSON DDL 要求失敗,並出現下列錯誤: Unrecognized JSON property: isPrivate. Check path ‘model.tables[1].isPrivate’, line 251, position 25.。
執行完成

透過 PowerShell 讀 MSMQ,以及在資料庫內,跨資料表與欄位找尋是否包含某個字串

朋友的需求:

$q= new-object System.Messaging.MessageQueue ‘機器名稱\private$\erptowf’
$msg=$q.Peek()

$s=new-object System.IO.StreamReader $msg.BodyStream

$s.ReadToEnd()

 

declare cur cursor
for select schema_name(o.schema_id) schName, object_name(c.object_id) tbName,c.name from sys.columns c
join sys.objects o on c.object_id=o.object_id
where TYPE_NAME(user_type_id) in (‘varchar’,’nvarchar’,’char’,’nchar’) and o.type=’u’ and o.is_ms_shipped=0

declare @sch sysname,@tb sysname,@col sysname
create table t(schemaname sysname,tablename sysname,columnname sysname)
open cur
fetch cur into @sch,@tb,@col
declare @sql nvarchar(max), @search nvarchar(100)=’58.’
while @@FETCH_STATUS =0
begin
    set @sql=’if exists(select * from [‘ + @sch + ‘].[‘ + @tb + ‘] where [‘ + @col + ‘] like “%’ + @search + ‘%") insert t values(“‘ +@sch + “‘,"‘ + @tb + “‘,"‘ + @col + “‘)’
    –print @sql
    exec(@sql)
    fetch cur into @sch,@tb,@col
end
close cur
deallocate cur
select * from t
drop table t

20151119 PowerShell Seminar 的 Sample Code 和 Slide

https://onedrive.live.com/?authkey=%21AM58hx2vOseWkhM&id=BF14192BD27975CB%21158225&cid=BF14192BD27975CB

透過 PowerShell 測試網路穩定性,並記錄到檔案中

改寫自以下的 QA,因為它似乎有點問題

https://community.spiceworks.com/topic/337701-ping-via-powershell-log-results-with-timestamp

 

[CmdletBinding()]
Param (
    [int32]$Count = 5,
    [int32]$Waittime=1,
    [Parameter(ValueFromPipeline=$true)]
    [String[]]$Computers = @("www.hinet.net","127.0.0.1"),
    [String]$LogPath = "c:\temp\pinglog.csv"
)

#若是透過 Pipeline 進來,會每個元素執行 Process 一次
process
{
    $Ping = @()
    #Test if path exists, if not, create it
    If (-not (Test-Path (Split-Path $LogPath) -PathType Container))
    {   Write-Verbose "Folder doesn’t exist $(Split-Path $LogPath), creating…"
        New-Item (Split-Path $LogPath) -ItemType Directory | Out-Null
    }

    #Test if log file exists, if not seed it with a header row
    If (-not (Test-Path $LogPath))
    {   Write-Verbose "Log file doesn’t exist: $($LogPath), creating…"
        Add-Content -Value ‘"TimeStamp","Source","Destination","IPV4Address","Status","ResponseTime"’ -Path $LogPath
    }

    #Log collection loop
    Write-Verbose "Beginning Ping monitoring of $Comptuer for $Count tries:"
    for($i=0;$i -lt $Count; $i++)
    {
        foreach($Computer in $Computers)
        {
            $Ping = Get-WmiObject Win32_PingStatus -Filter "Address = ‘$Computer’" | Select @{Label="TimeStamp";Expression={Get-Date}},@{Label="Source";Expression={ $_.__Server }},@{Label="Destination";Expression={ $_.Address }},IPv4Address,@{Label="Status";Expression={ If ($_.StatusCode -ne 0) {"Failed"} Else {"Ok"}}},ResponseTime
            $Result = $Ping | Select TimeStamp,Source,Destination,IPv4Address,Status,ResponseTime | ConvertTo-Csv -NoTypeInformation
            $Result[1] | Add-Content -Path $LogPath
            Write-verbose ($Ping | Select TimeStamp,Source,Destination,IPv4Address,Status,ResponseTime | Format-Table -AutoSize | Out-String)
        }   
        Start-Sleep -Seconds $Waittime
    }
}

 

執行範例如下:

.\PingTest.ps1 -LogPath "C:\temp\pingres.txt" -Computers @("www.hinet.net","127.0.0.1") -verbose

透過 PowerShell FTP 下載某個目錄內的檔案

朋友的需求

FTP 利用了 .NET 產生了自我簽章的憑證,要求 FTP 存取時需啟動 SSL

image

#讀取定義設定
param(
[string]$Username = “SQL2014\a",
[string]$Password = “P@ssw0rd1234″,
[string]$folderPath="ftp://localhost/",
[string]$target = “c:\temp\ftp\",
[string]$ErrorLogFile="c:\temp\ftp\errorlog.txt",
[bool]$logDetail=$true, #是否要存 Exception 的細節
[bool]$ssl=$true
)

function Get-FtpDir ($url,$credentials,[bool]$ssl) {
$request = [Net.WebRequest]::Create($url)
if($ssl)
{
#不信任根憑證也強制下載
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$request.EnableSsl = $True #ftp 可以啟動 SSL
}
$request.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory
if ($credentials) { $request.Credentials = $credentials }
$response = $request.GetResponse()
$reader = New-Object IO.StreamReader $response.GetResponseStream()
$reader.ReadToEnd()
$reader.Close()
$response.Close()
}

$credential=new-object System.Net.NetworkCredential($Username, $Password)
$files=Get-FTPDir $folderPath $credential $ssl
$webclient = New-Object System.Net.WebClient
$webclient.Credentials =$credential
Foreach ($file in ($files.Split(“`r`n")  |  `

where {$_ -like “*.txt*"}))
{
$source=$folderPath+$file
$dest=$target+$file
try{
$WebClient.DownloadFile($source, $dest)
}
catch
{
$d=Get-Date
$sb= New-Object -TypeName “System.Text.StringBuilder"
[Void]$sb.Append(“***" + $source + “`t" + $d.ToString() + “`t" + $Message + “***" + “`r`n")
if($error -ne $null -and $logDetail)
{
foreach($err in $error)
{
if($err -ne $null){[Void]$sb.Append($err.ToString()+ “`r`n")}
if($err.Exception -ne $null){[Void]$sb.Append($err.Exception.ToString() + “`r`n")}
}
$error.Clear()
}
($sb.ToString() + “`r`n") | Out-File $ErrorLogFile -Append
Write-Host $sb.ToString() -ForegroundColor Red
}
}

查詢系統當下的 Process 並存到 DB

Param
(
    [string]$cnn="Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI",
    [int]$period=300
)
$sqlConnection = new-object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $cnn
$sqlConnection.Open()

$sqlCommand = new-object System.Data.SqlClient.SqlCommand
$sqlCommand.CommandText="
if not exists(select * from sys.objects where name=’tbProcess’ and type=’u’)
    create table tbProcess(pk int identity primary key,
    id int,
    name nvarchar(500),
    path nvarchar(1000),
    pollingTime datetime2(3) default(sysdatetime())
    )
"
$sqlCommand.Connection=$sqlConnection
$sqlCommand.ExecuteNonQuery()

$sqlCommand.CommandText="insert tbProcess(id,name,path) values(@id,@name,@path)"
$sqlPar=new-object Data.SqlClient.SqlParameter("id",[System.Data.SqlDbType]::Int)
$sqlCommand.Parameters.Add($sqlPar) | Out-Null
$sqlPar2=new-object System.Data.SqlClient.SqlParameter("name",[System.Data.SqlDbType]::NVarChar,500)
$sqlCommand.Parameters.Add($sqlPar2) | Out-Null
$sqlPar3=new-object System.Data.SqlClient.SqlParameter("path",[System.Data.SqlDbType]::NVarChar,1000)
$sqlCommand.Parameters.Add($sqlPar3) | Out-Null

$d=get-date
for([int]$i=0;$i -le $period;$i++ )
{
    $processes=Get-Process
    foreach($process in $processes)
    {
        $sqlCommand.Parameters[0].Value=$process.Id
        $sqlCommand.Parameters[1].Value=$process.Name
        if($process.Path -eq $null)
        {
            $sqlCommand.Parameters[2].Value=""
        }
        else
        {
            $sqlCommand.Parameters[2].Value=$process.path
        }
        $sqlCommand.ExecuteNonQuery() | Out-Null
    }
    [System.Threading.Thread]::Sleep(1000)
    Write-Host $i
}
$sqlConnection.Close()

將簡單的 SSIS 2014 封裝轉成 2012 版本

有 30 來個相同的封裝,僅是內含執行封裝工作呼叫其他的封裝,但用的是 VS2013/BIDS 2014 開發的,但實際的 SQL Server 卻是 2012,所以簡單寫段 PowerShell 取代

$source=’C:\temp\ssis\2014\Integration Services 專案1\’
$files=dir ($source + ‘*.dtsx’)
$dest=Join-Path $source ‘temp\’
foreach($file in $files)
{
(((((Get-Content $file.FullName -encoding utf8 ) `
-replace “Microsoft.Package", “SSIS.Package.3″) `
-replace “12.0.2456.0″,"11.0.5058.0″) `
-replace ‘DTS:VersionBuild="2″‘,’DTS:VersionBuild="1″‘) `
-replace ‘DTS:Name="PackageFormatVersion">8</DTS:Property>’,’DTS:Name="PackageFormatVersion">6</DTS:Property>’ `
-replace ‘Microsoft.ExecutePackageTask’,’SSIS.ExecutePackageTask.3′) `
| Out-File (join-path $dest $file.Name) -encoding utf8
}

若封裝複雜,或是 2012(Attribute centric)換 2008 R2(Element centric)就完全不可能這麼做了,升級或選用封裝開發版本務必小心

透過 PowerShell 部署 AS DB 和 rdl 到 SharePoint

SQL 制式教材內,透過 PowerShell 部署 AS DB 和 rdl 到 SharePoint 的 Script 可以參考:

#執行 xmla 部署 AS DB

Import-module sqlps
Import-module sqlascmdlets
Invoke-ASCmd -Inputfile:"$env:SUBDIR\SetupFiles\SetupSSAS.xmla" -Server:"localhost"

 

#部署  rdl 到 SharePoint

Add-PSSnapin Microsoft.SharePoint.PowerShell
$siteName = "http://localhost/sites/adventureworks"
$docLibName = "Reports"

$web = Get-SPWeb $siteName
$docLib = $web.Lists[$docLibName]
$rootFolder = $docLib.RootFolder
$folderURL = $rootFolder.Url
$folder = $web.GetFolder($folderURL)
foreach ($file in $folder.Files) {
  $docLib.Items.DeleteItemById($file.Item.Id)
}

$subs = @()

foreach($subfolder in $folder.SubFolders) {
  if ($subfolder.Name -ne "Forms") {
      $subs = $subs + $subfolder.Url
  }
}

foreach ($url in $subs) {
  $folder.SubFolders.Delete($url)
}

$folderName = "$env:SUBDIR\SetupFiles\Reports"
$files =  ([System.IO.DirectoryInfo] (Get-Item $folderName)).GetFiles() | ForEach-Object {
    $fileStream = ([System.IO.FileInfo] (Get-Item $_.FullName)).OpenRead()
    $content = New-Object byte[] $fileStream.Length
    $fileStream.Read($content, 0, [int]$fileStream.Length);
    $fileStream.Close()
    $spFile = $rootFolder.Files.Add($rootFolder.Url + "/" + $_.Name, $content, $true)
}